Compare commits

..

6 Commits

Author SHA1 Message Date
Alexander Neumann
594f155eb6 Add version for 0.13.1 2022-04-10 10:58:55 +02:00
Alexander Neumann
90f1a9b5f5 Generate CHANGELOG.md for 0.13.1 2022-04-10 10:58:22 +02:00
Alexander Neumann
2ad3d50535 Prepare changelog for 0.13.1 2022-04-10 10:58:22 +02:00
Charlie Jiang
59fd21e30e Fix rclone (scoop shim) and sftp issue due to detached console on Windows 2022-04-10 10:56:55 +02:00
greatroar
c31f1e797b Cast unix.Statfs_t.Type to int64 when checking for btrfs
Fixes #3687. Uses the cast suggested by @MichaelEischer, except that the
contant isn't cast along, because it's untyped and will be converted by
the compiler as necessary.
2022-04-10 10:56:37 +02:00
Alexander Neumann
53ac0bfe85 Fix diff
Nodes in trees were always printed with a `+` in diff, regardless of
whether or not a dir was added or removed. Let's use the mode we were
passed in printDir().

Closes #3685
2022-04-10 10:56:19 +02:00
328 changed files with 5818 additions and 10378 deletions

2
.gitattributes vendored
View File

@@ -1,2 +0,0 @@
# Workaround for https://github.com/golang/go/issues/52268.
**/testdata/fuzz/*/* eol=lf

View File

@@ -9,7 +9,7 @@ on:
pull_request:
env:
latest_go: "1.19.x"
latest_go: "1.18.x"
GO111MODULE: on
jobs:
@@ -19,30 +19,24 @@ jobs:
# list of jobs to run:
include:
- job_name: Windows
go: 1.19.x
go: 1.18.x
os: windows-latest
install_verb: install
- job_name: macOS
go: 1.19.x
go: 1.18.x
os: macOS-latest
test_fuse: false
install_verb: install
- job_name: Linux
go: 1.19.x
go: 1.18.x
os: ubuntu-latest
test_cloud_backends: true
test_fuse: true
check_changelog: true
install_verb: install
- job_name: Linux
go: 1.18.x
os: ubuntu-latest
test_fuse: true
install_verb: install
- job_name: Linux
go: 1.17.x
os: ubuntu-latest
@@ -61,6 +55,12 @@ jobs:
test_fuse: true
install_verb: get
- job_name: Linux
go: 1.14.x
os: ubuntu-latest
test_fuse: true
install_verb: get
name: ${{ matrix.job_name }} Go ${{ matrix.go }}
runs-on: ${{ matrix.os }}
@@ -266,7 +266,7 @@ jobs:
uses: golangci/golangci-lint-action@v2
with:
# Required: the version of golangci-lint is required and must be specified without patch version: we always use the latest patch version.
version: v1.48
version: v1.45
# Optional: show only new issues if it's a pull request. The default value is `false`.
only-new-issues: true
args: --verbose --timeout 5m

View File

@@ -24,7 +24,7 @@ linters:
- govet
# make sure names and comments are used according to the conventions
- revive
- golint
# detect when assignments to existing variables are not used
- ineffassign
@@ -51,7 +51,7 @@ issues:
# list of things to not warn about
exclude:
# revive: do not warn about missing comments for exported stuff
- exported (function|method|var|type|const) .* should have comment or be unexported
# revive: ignore constants in all caps
# golint: do not warn about missing comments for exported stuff
- exported (function|method|var|type|const) `.*` should have comment or be unexported
# golint: ignore constants in all caps
- don't use ALL_CAPS in Go names; use CamelCase

View File

@@ -1,452 +1,38 @@
Changelog for restic 0.14.0 (2022-08-25)
Changelog for restic 0.13.1 (2022-04-10)
=======================================
The following sections list the changes in restic 0.14.0 relevant to
The following sections list the changes in restic 0.13.1 relevant to
restic users. The changes are ordered by importance.
Summary
-------
* Fix #2248: Support `self-update` on Windows
* Fix #3428: List snapshots in backend at most once to resolve snapshot IDs
* Fix #3432: Fix rare 'not found in repository' error for `copy` command
* Fix #3685: The `diff` command incorrectly listed some files as added
* Fix #3681: Fix rclone (shimmed by Scoop) and sftp not working on Windows
* Fix #3720: Directory sync errors for repositories accessed via SMB
* Fix #3736: The `stats` command miscalculated restore size for multiple snapshots
* Fix #3861: Yield error on invalid policy to `forget`
* Fix #3716: Print "wrong password" to stderr instead of stdout
* Fix #3772: Correctly rebuild index for legacy repositories
* Fix #3776: Limit number of key files tested while opening a repository
* Chg #1842: Support debug log creation in release builds
* Chg #3295: Deprecate `check --check-unused` and add further checks
* Chg #3680: Update dependencies and require Go 1.15 or newer
* Chg #3742: Replace `--repo2` option used by `init`/`copy` with `--from-repo`
* Enh #1153: Support pruning even when the disk is full
* Enh #21: Add compression support
* Enh #2162: Adaptive IO concurrency based on backend connections
* Enh #2291: Allow pack size customization
* Enh #2295: Allow use of SAS token to authenticate to Azure
* Enh #2696: Improve backup speed with many small files
* Enh #2907: Make snapshot directory structure of `mount` command customizable
* Enh #3114: Optimize handling of duplicate blobs in `prune`
* Enh #3465: Improve handling of temporary files on Windows
* Enh #3709: Validate exclude patterns before backing up
* Enh #3837: Improve SFTP repository initialization over slow links
* Enh #2351: Use config file permissions to control file group access
* Enh #3475: Allow limiting IO concurrency for local and SFTP backend
* Enh #3484: Stream data in `check` and `prune` commands
* Enh #2923: Improve speed of `copy` command
* Enh #3729: Display full IDs in `check` warnings
* Enh #3773: Optimize memory usage for directories with many files
* Enh #3819: Validate include/exclude patterns before restoring
* Fix #3685: Fix the diff command
* Fix #3681: Fix rclone (shimmed by Scoop) and sftp stopped working on Windows
Details
-------
* Bugfix #2248: Support `self-update` on Windows
* Bugfix #3685: Fix the diff command
Restic `self-update` would fail in situations where the operating system locks running
binaries, including Windows. The new behavior works around this by renaming the running file
and swapping the updated file in place.
https://github.com/restic/restic/issues/2248
https://github.com/restic/restic/pull/3675
* Bugfix #3428: List snapshots in backend at most once to resolve snapshot IDs
Many commands support specifying a list of snapshot IDs which are then used to determine the
snapshots to be processed by the command. To resolve snapshot IDs or `latest`, and check that
these exist, restic previously listed all snapshots stored in the repository. Depending on
the backend this could be a slow and/or expensive operation.
Restic now lists the snapshots only once and remembers the result in order to resolve all
further snapshot IDs swiftly.
https://github.com/restic/restic/issues/3428
https://github.com/restic/restic/pull/3570
https://github.com/restic/restic/pull/3395
* Bugfix #3432: Fix rare 'not found in repository' error for `copy` command
In rare cases `copy` (and other commands) would report that `LoadTree(...)` returned an `id
[...] not found in repository` error. This could be caused by a backup or copy command running
concurrently. The error was only temporary; running the failed restic command a second time as
a workaround did resolve the error.
This issue has now been fixed by correcting the order in which restic reads data from the
repository. It is now guaranteed that restic only loads snapshots for which all necessary data
is already available.
https://github.com/restic/restic/issues/3432
https://github.com/restic/restic/pull/3570
* Bugfix #3685: The `diff` command incorrectly listed some files as added
There was a bug in the `diff` command, causing it to always show files in a removed directory as
added. This has now been fixed.
There was a bug in the `diff` command, it would always show files in a removed directory as added.
We've fixed that.
https://github.com/restic/restic/issues/3685
https://github.com/restic/restic/pull/3686
* Bugfix #3681: Fix rclone (shimmed by Scoop) and sftp not working on Windows
* Bugfix #3681: Fix rclone (shimmed by Scoop) and sftp stopped working on Windows
In #3602 a fix was introduced to address the problem of `rclone` prematurely exiting when
Ctrl+C is pressed on Windows. The solution was to create the subprocess with its console
detached from the restic console.
However, this solution failed when using `rclone` installed by Scoop or using `sftp` with a
passphrase-protected private key. We've now fixed this by using a different approach to
prevent Ctrl-C from passing down too early.
In #3602 a fix was introduced to fix the problem that rclone prematurely exits when Ctrl+C is
pressed on Windows. The solution was to create the subprocess with its console detached from
the restic console. However, such solution fails when using rclone install by scoop or using
sftp with a passphrase- protected private key. We've fixed that by using a different approach
to prevent Ctrl-C from passing down too early.
https://github.com/restic/restic/issues/3681
https://github.com/restic/restic/issues/3692
https://github.com/restic/restic/pull/3696
* Bugfix #3720: Directory sync errors for repositories accessed via SMB
On Linux and macOS, accessing a repository via a SMB/CIFS mount resulted in restic failing to
save the lock file, yielding the following errors:
Save(<lock/071fe833f0>) returned error, retrying after 552.330144ms: sync /repo/locks:
no such file or directory Save(<lock/bf789d7343>) returned error, retrying after
552.330144ms: sync /repo/locks: invalid argument
This has now been fixed by ignoring the relevant error codes.
https://github.com/restic/restic/issues/3720
https://github.com/restic/restic/issues/3751
https://github.com/restic/restic/pull/3752
* Bugfix #3736: The `stats` command miscalculated restore size for multiple snapshots
Since restic 0.10.0 the restore size calculated by the `stats` command for multiple snapshots
was too low. The hardlink detection was accidentally applied across multiple snapshots and
thus ignored many files. This has now been fixed.
https://github.com/restic/restic/issues/3736
https://github.com/restic/restic/pull/3740
* Bugfix #3861: Yield error on invalid policy to `forget`
The `forget` command previously silently ignored invalid/unsupported units in the duration
options, such as e.g. `--keep-within-daily 2w`.
Specifying an invalid/unsupported duration unit now results in an error.
https://github.com/restic/restic/issues/3861
https://github.com/restic/restic/pull/3862
* Bugfix #3716: Print "wrong password" to stderr instead of stdout
If an invalid password was entered, the error message was printed on stdout and not on stderr as
intended. This has now been fixed.
https://github.com/restic/restic/pull/3716
https://forum.restic.net/t/4965
* Bugfix #3772: Correctly rebuild index for legacy repositories
After running `rebuild-index` on a legacy repository containing mixed pack files (that is,
pack files which store both metadata and file data), `check` printed warnings like `pack
12345678 contained in several indexes: ...`. This warning was not critical, but has now
nonetheless been fixed by properly handling mixed pack files while rebuilding the index.
Running `prune` for such legacy repositories will also fix the warning by reorganizing the
pack files which caused it.
https://github.com/restic/restic/pull/3772
https://github.com/restic/restic/pull/3884
https://forum.restic.net/t/5044/13
* Bugfix #3776: Limit number of key files tested while opening a repository
Previously, restic tested the password against every key in the repository when opening a
repository. The more keys there were in the repository, the slower this operation became.
Restic now tests the password against up to 20 key files in the repository. Alternatively, you
can use the `--key-hint=<key ID>` option to specify a specific key file to use instead.
https://github.com/restic/restic/pull/3776
* Change #1842: Support debug log creation in release builds
Creating a debug log was only possible in debug builds which required users to manually build
restic. We changed the release builds to allow creating debug logs by simply setting the
environment variable `DEBUG_LOG=logname.log`.
https://github.com/restic/restic/issues/1842
https://github.com/restic/restic/pull/3826
* Change #3295: Deprecate `check --check-unused` and add further checks
Since restic 0.12.0, it is expected to still have unused blobs after running `prune`. This made
the `--check-unused` option of the `check` command rather useless and tended to confuse
users. This option has been deprecated and is now ignored.
The `check` command now also warns if a repository is using either the legacy S3 layout or mixed
pack files with both tree and data blobs. The latter is known to cause performance problems.
https://github.com/restic/restic/issues/3295
https://github.com/restic/restic/pull/3730
* Change #3680: Update dependencies and require Go 1.15 or newer
We've updated most dependencies. Since some libraries require newer language features we're
dropping support for Go 1.14, which means that restic now requires at least Go 1.15 to build.
https://github.com/restic/restic/issues/3680
https://github.com/restic/restic/issues/3883
* Change #3742: Replace `--repo2` option used by `init`/`copy` with `--from-repo`
The `init` and `copy` commands can read data from another repository. However, confusingly
`--repo2` referred to the repository *from* which the `init` command copies parameters, but
for the `copy` command `--repo2` referred to the copy *destination*.
We've introduced a new option, `--from-repo`, which always refers to the source repository
for both commands. The old parameter names have been deprecated but still work. To create a new
repository and copy all snapshots to it, the commands are now as follows:
``` restic -r /srv/restic-repo-copy init --from-repo /srv/restic-repo
--copy-chunker-params restic -r /srv/restic-repo-copy copy --from-repo
/srv/restic-repo ```
https://github.com/restic/restic/pull/3742
https://forum.restic.net/t/5017
* Enhancement #1153: Support pruning even when the disk is full
When running out of disk space it was no longer possible to add or remove data from a repository.
To help with recovering from such a deadlock, the prune command now supports an
`--unsafe-recover-no-free-space` option to recover from these situations. Make sure to
read the documentation first!
https://github.com/restic/restic/issues/1153
https://github.com/restic/restic/pull/3481
* Enhancement #21: Add compression support
We've added compression support to the restic repository format. To create a repository using
the new format run `init --repository-version 2`. Please note that the repository cannot be
read by restic versions prior to 0.14.0.
You can configure whether data is compressed with the option `--compression`. It can be set to
`auto` (the default, which will compress very fast), `max` (which will trade backup speed and
CPU usage for better compression), or `off` (which disables compression). Each setting is
only applied for the current run of restic and does *not* apply to future runs. The option can
also be set via the environment variable `RESTIC_COMPRESSION`.
To upgrade in place run `migrate upgrade_repo_v2` followed by `prune`. See the documentation
for more details. The migration checks the repository integrity and upgrades the repository
format, but will not change any data. Afterwards, prune will rewrite the metadata to make use of
compression.
As an alternative you can use the `copy` command to migrate snapshots; First create a new
repository using `init --repository-version 2 --copy-chunker-params --repo2
path/to/old/repo`, and then use the `copy` command to copy all snapshots to the new
repository.
https://github.com/restic/restic/issues/21
https://github.com/restic/restic/issues/3779
https://github.com/restic/restic/pull/3666
https://github.com/restic/restic/pull/3704
https://github.com/restic/restic/pull/3733
* Enhancement #2162: Adaptive IO concurrency based on backend connections
Many commands used hard-coded limits for the number of concurrent operations. This prevented
speed improvements by increasing the number of connections used by a backend.
These limits have now been replaced by using the configured number of backend connections
instead, which can be controlled using the `-o <backend-name>.connections=5` option.
Commands will then automatically scale their parallelism accordingly.
To limit the number of CPU cores used by restic, you can set the environment variable
`GOMAXPROCS` accordingly. For example to use a single CPU core, use `GOMAXPROCS=1`.
https://github.com/restic/restic/issues/2162
https://github.com/restic/restic/issues/1467
https://github.com/restic/restic/pull/3611
* Enhancement #2291: Allow pack size customization
Restic now uses a target pack size of 16 MiB by default. This can be customized using the
`--pack-size size` option. Supported pack sizes range between 4 and 128 MiB.
It is possible to migrate an existing repository to _larger_ pack files using `prune
--repack-small`. This will rewrite every pack file which is significantly smaller than the
target size.
https://github.com/restic/restic/issues/2291
https://github.com/restic/restic/pull/3731
* Enhancement #2295: Allow use of SAS token to authenticate to Azure
Previously restic only supported AccountKeys to authenticate to Azure storage accounts,
which necessitates giving a significant amount of access.
We added support for Azure SAS tokens which are a more fine-grained and time-limited manner of
granting access. Set the `AZURE_ACCOUNT_NAME` and `AZURE_ACCOUNT_SAS` environment
variables to use a SAS token for authentication. Note that if `AZURE_ACCOUNT_KEY` is set, it
will take precedence.
https://github.com/restic/restic/issues/2295
https://github.com/restic/restic/pull/3661
* Enhancement #2696: Improve backup speed with many small files
We have restructured the backup pipeline to continue reading files while all upload
connections are busy. This allows the backup to already prepare the next data file such that the
upload can continue as soon as a connection becomes available. This can especially improve the
backup performance for high latency backends.
The upload concurrency is now controlled using the `-o <backend-name>.connections=5`
option.
https://github.com/restic/restic/issues/2696
https://github.com/restic/restic/pull/3489
* Enhancement #2907: Make snapshot directory structure of `mount` command customizable
We've added the possibility to customize the snapshot directory structure of the `mount`
command using templates passed to the `--snapshot-template` option. The formatting of
snapshots' timestamps is now controlled using `--time-template` and supports
subdirectories to for example group snapshots by year. Please see `restic help mount` for
further details.
Characters in tag names which are not allowed in a filename are replaced by underscores `_`. For
example a tag `foo/bar` will result in a directory name of `foo_bar`.
https://github.com/restic/restic/issues/2907
https://github.com/restic/restic/pull/2913
https://github.com/restic/restic/pull/3691
* Enhancement #3114: Optimize handling of duplicate blobs in `prune`
Restic `prune` always used to repack all data files containing duplicate blobs. This
effectively removed all duplicates during prune. However, as a consequence all these data
files were repacked even if the unused repository space threshold could be reached with less
work.
This is now changed and `prune` works nice and fast even when there are lots of duplicate blobs.
https://github.com/restic/restic/issues/3114
https://github.com/restic/restic/pull/3290
* Enhancement #3465: Improve handling of temporary files on Windows
In some cases restic failed to delete temporary files, causing the current command to fail.
This has now been fixed by ensuring that Windows automatically deletes the file. In addition,
temporary files are only written to disk when necessary, reducing disk writes.
https://github.com/restic/restic/issues/3465
https://github.com/restic/restic/issues/1551
https://github.com/restic/restic/pull/3610
* Enhancement #3709: Validate exclude patterns before backing up
Exclude patterns provided via `--exclude`, `--iexclude`, `--exclude-file` or
`--iexclude-file` previously weren't validated. As a consequence, invalid patterns
resulted in files that were meant to be excluded being backed up.
Restic now validates all patterns before running the backup and aborts with a fatal error if an
invalid pattern is detected.
https://github.com/restic/restic/issues/3709
https://github.com/restic/restic/pull/3734
* Enhancement #3837: Improve SFTP repository initialization over slow links
The `init` command, when used on an SFTP backend, now sends multiple `mkdir` commands to the
backend concurrently. This reduces the waiting times when creating a repository over a very
slow connection.
https://github.com/restic/restic/issues/3837
https://github.com/restic/restic/pull/3840
* Enhancement #2351: Use config file permissions to control file group access
Previously files in a local/SFTP repository would always end up with very restrictive access
permissions, allowing access only to the owner. This prevented a number of valid use-cases
involving groups and ACLs.
We now use the permissions of the config file in the repository to decide whether group access
should be given to newly created repository files or not. We arrange for repository files to be
created group readable exactly when the repository config file is group readable.
To opt-in to group readable repositories, a simple `chmod -R g+r` or equivalent on the config
file can be used. For repositories that should be writable by group members a tad more setup is
required, see the docs.
Posix ACLs can also be used now that the group permissions being forced to zero no longer masks
the effect of ACL entries.
https://github.com/restic/restic/issues/2351
https://github.com/restic/restic/pull/3419
https://forum.restic.net/t/1391
* Enhancement #3475: Allow limiting IO concurrency for local and SFTP backend
Restic did not support limiting the IO concurrency / number of connections for accessing
repositories stored using the local or SFTP backends. The number of connections is now limited
as for other backends, and can be configured via the the `-o local.connections=2` and `-o
sftp.connections=5` options. This ensures that restic does not overwhelm the backend with
concurrent IO operations.
https://github.com/restic/restic/pull/3475
* Enhancement #3484: Stream data in `check` and `prune` commands
The commands `check --read-data` and `prune` previously downloaded data files into
temporary files which could end up being written to disk. This could cause a large amount of data
being written to disk.
The pack files are now instead streamed, which removes the need for temporary files. Please
note that *uploads* during `backup` and `prune` still require temporary files.
https://github.com/restic/restic/issues/3710
https://github.com/restic/restic/pull/3484
https://github.com/restic/restic/pull/3717
* Enhancement #2923: Improve speed of `copy` command
The `copy` command could require a long time to copy snapshots for non-local backends. This has
been improved to provide a throughput comparable to the `restore` command.
Additionally, `copy` now displays a progress bar.
https://github.com/restic/restic/issues/2923
https://github.com/restic/restic/pull/3513
* Enhancement #3729: Display full IDs in `check` warnings
When running commands to inspect or repair a damaged repository, it is often necessary to
supply the full IDs of objects stored in the repository.
The output of `check` now includes full IDs instead of their shortened variant.
https://github.com/restic/restic/pull/3729
* Enhancement #3773: Optimize memory usage for directories with many files
Backing up a directory with hundreds of thousands or more files caused restic to require large
amounts of memory. We've now optimized the `backup` command such that it requires up to 30% less
memory.
https://github.com/restic/restic/pull/3773
* Enhancement #3819: Validate include/exclude patterns before restoring
Patterns provided to `restore` via `--exclude`, `--iexclude`, `--include` and
`--iinclude` weren't validated before running the restore. Invalid patterns would result in
error messages being printed repeatedly, and possibly unwanted files being restored.
Restic now validates all patterns before running the restore, and aborts with a fatal error if
an invalid pattern is detected.
https://github.com/restic/restic/pull/3819
Changelog for restic 0.13.0 (2022-03-26)
=======================================

View File

@@ -48,8 +48,9 @@ environment was used and so on. Please tell us at least the following things:
Remember, the easier it is for us to reproduce the bug, the earlier it will be
corrected!
In addition, you can instruct restic to create a debug log by setting the
environment variable `DEBUG_LOG` to a file, e.g. like this:
In addition, you can compile restic with debug support by running
`go run build.go -tags debug` and instructing it to create a debug
log by setting the environment variable `DEBUG_LOG` to a file, e.g. like this:
$ export DEBUG_LOG=/tmp/restic-debug.log
$ restic backup ~/work
@@ -65,8 +66,8 @@ Development Environment
The repository contains the code written for restic in the directories
`cmd/` and `internal/`.
Make sure you have the minimum required Go version installed. Clone the repo
(without having `$GOPATH` set) and `cd` into the directory:
Restic requires Go version 1.14 or later for compiling. Clone the repo (without
having `$GOPATH` set) and `cd` into the directory:
$ unset GOPATH
$ git clone https://github.com/restic/restic

View File

@@ -1 +1 @@
0.14.0
0.13.1

View File

@@ -0,0 +1,7 @@
Bugfix: Fix the diff command
There was a bug in the `diff` command, it would always show files in a removed
directory as added. We've fixed that.
https://github.com/restic/restic/issues/3685
https://github.com/restic/restic/pull/3686

View File

@@ -0,0 +1,12 @@
Bugfix: Fix rclone (shimmed by Scoop) and sftp stopped working on Windows
In #3602 a fix was introduced to fix the problem that rclone prematurely exits
when Ctrl+C is pressed on Windows. The solution was to create the subprocess
with its console detached from the restic console. However, such solution
fails when using rclone install by scoop or using sftp with a passphrase-
protected private key. We've fixed that by using a different approach to prevent
Ctrl-C from passing down too early.
https://github.com/restic/restic/issues/3681
https://github.com/restic/restic/issues/3692
https://github.com/restic/restic/pull/3696

View File

@@ -1,9 +0,0 @@
Enhancement: Support pruning even when the disk is full
When running out of disk space it was no longer possible to add or remove
data from a repository. To help with recovering from such a deadlock, the
prune command now supports an `--unsafe-recover-no-free-space` option to
recover from these situations. Make sure to read the documentation first!
https://github.com/restic/restic/issues/1153
https://github.com/restic/restic/pull/3481

View File

@@ -1,8 +0,0 @@
Change: Support debug log creation in release builds
Creating a debug log was only possible in debug builds which required users to
manually build restic. We changed the release builds to allow creating debug
logs by simply setting the environment variable `DEBUG_LOG=logname.log`.
https://github.com/restic/restic/issues/1842
https://github.com/restic/restic/pull/3826

View File

@@ -1,28 +0,0 @@
Enhancement: Add compression support
We've added compression support to the restic repository format. To create a
repository using the new format run `init --repository-version 2`. Please note
that the repository cannot be read by restic versions prior to 0.14.0.
You can configure whether data is compressed with the option `--compression`. It
can be set to `auto` (the default, which will compress very fast), `max` (which
will trade backup speed and CPU usage for better compression), or `off` (which
disables compression). Each setting is only applied for the current run of restic
and does *not* apply to future runs. The option can also be set via the
environment variable `RESTIC_COMPRESSION`.
To upgrade in place run `migrate upgrade_repo_v2` followed by `prune`. See the
documentation for more details. The migration checks the repository integrity
and upgrades the repository format, but will not change any data. Afterwards,
prune will rewrite the metadata to make use of compression.
As an alternative you can use the `copy` command to migrate snapshots; First
create a new repository using
`init --repository-version 2 --copy-chunker-params --repo2 path/to/old/repo`,
and then use the `copy` command to copy all snapshots to the new repository.
https://github.com/restic/restic/issues/21
https://github.com/restic/restic/issues/3779
https://github.com/restic/restic/pull/3666
https://github.com/restic/restic/pull/3704
https://github.com/restic/restic/pull/3733

View File

@@ -1,18 +0,0 @@
Enhancement: Adaptive IO concurrency based on backend connections
Many commands used hard-coded limits for the number of concurrent operations.
This prevented speed improvements by increasing the number of connections used
by a backend.
These limits have now been replaced by using the configured number of backend
connections instead, which can be controlled using the
`-o <backend-name>.connections=5` option. Commands will then automatically
scale their parallelism accordingly.
To limit the number of CPU cores used by restic, you can set the environment
variable `GOMAXPROCS` accordingly. For example to use a single CPU core, use
`GOMAXPROCS=1`.
https://github.com/restic/restic/issues/2162
https://github.com/restic/restic/issues/1467
https://github.com/restic/restic/pull/3611

View File

@@ -1,8 +0,0 @@
Bugfix: Support `self-update` on Windows
Restic `self-update` would fail in situations where the operating system
locks running binaries, including Windows. The new behavior works around
this by renaming the running file and swapping the updated file in place.
https://github.com/restic/restic/issues/2248
https://github.com/restic/restic/pull/3675

View File

@@ -1,12 +0,0 @@
Enhancement: Allow pack size customization
Restic now uses a target pack size of 16 MiB by default. This can be customized
using the `--pack-size size` option. Supported pack sizes range between 4 and
128 MiB.
It is possible to migrate an existing repository to _larger_ pack files using
`prune --repack-small`. This will rewrite every pack file which is
significantly smaller than the target size.
https://github.com/restic/restic/issues/2291
https://github.com/restic/restic/pull/3731

View File

@@ -1,14 +0,0 @@
Enhancement: Allow use of SAS token to authenticate to Azure
Previously restic only supported AccountKeys to authenticate to Azure
storage accounts, which necessitates giving a significant amount of
access.
We added support for Azure SAS tokens which are a more fine-grained
and time-limited manner of granting access. Set the `AZURE_ACCOUNT_NAME`
and `AZURE_ACCOUNT_SAS` environment variables to use a SAS token for
authentication. Note that if `AZURE_ACCOUNT_KEY` is set, it will take
precedence.
https://github.com/restic/restic/issues/2295
https://github.com/restic/restic/pull/3661

View File

@@ -1,13 +0,0 @@
Enhancement: Improve backup speed with many small files
We have restructured the backup pipeline to continue reading files while all
upload connections are busy. This allows the backup to already prepare the next
data file such that the upload can continue as soon as a connection becomes
available. This can especially improve the backup performance for high latency
backends.
The upload concurrency is now controlled using the `-o <backend-name>.connections=5`
option.
https://github.com/restic/restic/issues/2696
https://github.com/restic/restic/pull/3489

View File

@@ -1,15 +0,0 @@
Enhancement: Make snapshot directory structure of `mount` command customizable
We've added the possibility to customize the snapshot directory structure of
the `mount` command using templates passed to the `--snapshot-template` option.
The formatting of snapshots' timestamps is now controlled using `--time-template`
and supports subdirectories to for example group snapshots by year. Please
see `restic help mount` for further details.
Characters in tag names which are not allowed in a filename are replaced by
underscores `_`. For example a tag `foo/bar` will result in a directory name
of `foo_bar`.
https://github.com/restic/restic/issues/2907
https://github.com/restic/restic/pull/2913
https://github.com/restic/restic/pull/3691

View File

@@ -1,12 +0,0 @@
Enhancement: Optimize handling of duplicate blobs in `prune`
Restic `prune` always used to repack all data files containing duplicate
blobs. This effectively removed all duplicates during prune. However, as a
consequence all these data files were repacked even if the unused repository
space threshold could be reached with less work.
This is now changed and `prune` works nice and fast even when there are lots
of duplicate blobs.
https://github.com/restic/restic/issues/3114
https://github.com/restic/restic/pull/3290

View File

@@ -1,13 +0,0 @@
Change: Deprecate `check --check-unused` and add further checks
Since restic 0.12.0, it is expected to still have unused blobs after running
`prune`. This made the `--check-unused` option of the `check` command rather
useless and tended to confuse users. This option has been deprecated and is
now ignored.
The `check` command now also warns if a repository is using either the legacy
S3 layout or mixed pack files with both tree and data blobs. The latter is
known to cause performance problems.
https://github.com/restic/restic/issues/3295
https://github.com/restic/restic/pull/3730

View File

@@ -1,14 +0,0 @@
Bugfix: List snapshots in backend at most once to resolve snapshot IDs
Many commands support specifying a list of snapshot IDs which are then used to
determine the snapshots to be processed by the command. To resolve snapshot IDs
or `latest`, and check that these exist, restic previously listed all snapshots
stored in the repository. Depending on the backend this could be a slow and/or
expensive operation.
Restic now lists the snapshots only once and remembers the result in order to
resolve all further snapshot IDs swiftly.
https://github.com/restic/restic/issues/3428
https://github.com/restic/restic/pull/3570
https://github.com/restic/restic/pull/3395

View File

@@ -1,14 +0,0 @@
Bugfix: Fix rare 'not found in repository' error for `copy` command
In rare cases `copy` (and other commands) would report that `LoadTree(...)`
returned an `id [...] not found in repository` error. This could be caused by
a backup or copy command running concurrently. The error was only temporary;
running the failed restic command a second time as a workaround did resolve the
error.
This issue has now been fixed by correcting the order in which restic reads data
from the repository. It is now guaranteed that restic only loads snapshots for
which all necessary data is already available.
https://github.com/restic/restic/issues/3432
https://github.com/restic/restic/pull/3570

View File

@@ -1,10 +0,0 @@
Enhancement: Improve handling of temporary files on Windows
In some cases restic failed to delete temporary files, causing the current
command to fail. This has now been fixed by ensuring that Windows automatically
deletes the file. In addition, temporary files are only written to disk when
necessary, reducing disk writes.
https://github.com/restic/restic/issues/3465
https://github.com/restic/restic/issues/1551
https://github.com/restic/restic/pull/3610

View File

@@ -1,7 +0,0 @@
Bugfix: The `diff` command incorrectly listed some files as added
There was a bug in the `diff` command, causing it to always show files in a
removed directory as added. This has now been fixed.
https://github.com/restic/restic/issues/3685
https://github.com/restic/restic/pull/3686

View File

@@ -1,13 +0,0 @@
Bugfix: Fix rclone (shimmed by Scoop) and sftp not working on Windows
In #3602 a fix was introduced to address the problem of `rclone` prematurely
exiting when Ctrl+C is pressed on Windows. The solution was to create the
subprocess with its console detached from the restic console.
However, this solution failed when using `rclone` installed by Scoop or using
`sftp` with a passphrase-protected private key. We've now fixed this by using
a different approach to prevent Ctrl-C from passing down too early.
https://github.com/restic/restic/issues/3681
https://github.com/restic/restic/issues/3692
https://github.com/restic/restic/pull/3696

View File

@@ -1,11 +0,0 @@
Enhancement: Validate exclude patterns before backing up
Exclude patterns provided via `--exclude`, `--iexclude`, `--exclude-file` or
`--iexclude-file` previously weren't validated. As a consequence, invalid
patterns resulted in files that were meant to be excluded being backed up.
Restic now validates all patterns before running the backup and aborts with
a fatal error if an invalid pattern is detected.
https://github.com/restic/restic/issues/3709
https://github.com/restic/restic/pull/3734

View File

@@ -1,13 +0,0 @@
Bugfix: Directory sync errors for repositories accessed via SMB
On Linux and macOS, accessing a repository via a SMB/CIFS mount resulted in
restic failing to save the lock file, yielding the following errors:
Save(<lock/071fe833f0>) returned error, retrying after 552.330144ms: sync /repo/locks: no such file or directory
Save(<lock/bf789d7343>) returned error, retrying after 552.330144ms: sync /repo/locks: invalid argument
This has now been fixed by ignoring the relevant error codes.
https://github.com/restic/restic/issues/3720
https://github.com/restic/restic/issues/3751
https://github.com/restic/restic/pull/3752

View File

@@ -1,8 +0,0 @@
Bugfix: The `stats` command miscalculated restore size for multiple snapshots
Since restic 0.10.0 the restore size calculated by the `stats` command for
multiple snapshots was too low. The hardlink detection was accidentally applied
across multiple snapshots and thus ignored many files. This has now been fixed.
https://github.com/restic/restic/issues/3736
https://github.com/restic/restic/pull/3740

View File

@@ -1,8 +0,0 @@
Enhancement: Improve SFTP repository initialization over slow links
The `init` command, when used on an SFTP backend, now sends multiple `mkdir`
commands to the backend concurrently. This reduces the waiting times when
creating a repository over a very slow connection.
https://github.com/restic/restic/issues/3837
https://github.com/restic/restic/pull/3840

View File

@@ -1,9 +0,0 @@
Bugfix: Yield error on invalid policy to `forget`
The `forget` command previously silently ignored invalid/unsupported
units in the duration options, such as e.g. `--keep-within-daily 2w`.
Specifying an invalid/unsupported duration unit now results in an error.
https://github.com/restic/restic/issues/3861
https://github.com/restic/restic/pull/3862

View File

@@ -1,21 +0,0 @@
Enhancement: Use config file permissions to control file group access
Previously files in a local/SFTP repository would always end up with very
restrictive access permissions, allowing access only to the owner. This
prevented a number of valid use-cases involving groups and ACLs.
We now use the permissions of the config file in the repository to decide
whether group access should be given to newly created repository files or
not. We arrange for repository files to be created group readable exactly
when the repository config file is group readable.
To opt-in to group readable repositories, a simple `chmod -R g+r` or
equivalent on the config file can be used. For repositories that should
be writable by group members a tad more setup is required, see the docs.
Posix ACLs can also be used now that the group permissions being forced to
zero no longer masks the effect of ACL entries.
https://github.com/restic/restic/issues/2351
https://github.com/restic/restic/pull/3419
https://forum.restic.net/t/1391

View File

@@ -1,9 +0,0 @@
Enhancement: Allow limiting IO concurrency for local and SFTP backend
Restic did not support limiting the IO concurrency / number of connections for
accessing repositories stored using the local or SFTP backends. The number of
connections is now limited as for other backends, and can be configured via the
the `-o local.connections=2` and `-o sftp.connections=5` options. This ensures
that restic does not overwhelm the backend with concurrent IO operations.
https://github.com/restic/restic/pull/3475

View File

@@ -1,13 +0,0 @@
Enhancement: Stream data in `check` and `prune` commands
The commands `check --read-data` and `prune` previously downloaded data files
into temporary files which could end up being written to disk. This could cause
a large amount of data being written to disk.
The pack files are now instead streamed, which removes the need for temporary
files. Please note that *uploads* during `backup` and `prune` still require
temporary files.
https://github.com/restic/restic/pull/3484
https://github.com/restic/restic/issues/3710
https://github.com/restic/restic/pull/3717

View File

@@ -1,10 +0,0 @@
Enhancement: Improve speed of `copy` command
The `copy` command could require a long time to copy snapshots for non-local
backends. This has been improved to provide a throughput comparable to the
`restore` command.
Additionally, `copy` now displays a progress bar.
https://github.com/restic/restic/issues/2923
https://github.com/restic/restic/pull/3513

View File

@@ -1,8 +0,0 @@
Change: Update dependencies and require Go 1.15 or newer
We've updated most dependencies. Since some libraries require newer language
features we're dropping support for Go 1.14, which means that restic now
requires at least Go 1.15 to build.
https://github.com/restic/restic/issues/3680
https://github.com/restic/restic/issues/3883

View File

@@ -1,7 +0,0 @@
Bugfix: Print "wrong password" to stderr instead of stdout
If an invalid password was entered, the error message was printed on stdout and
not on stderr as intended. This has now been fixed.
https://github.com/restic/restic/pull/3716
https://forum.restic.net/t/4965

View File

@@ -1,8 +0,0 @@
Enhancement: Display full IDs in `check` warnings
When running commands to inspect or repair a damaged repository, it is often
necessary to supply the full IDs of objects stored in the repository.
The output of `check` now includes full IDs instead of their shortened variant.
https://github.com/restic/restic/pull/3729

View File

@@ -1,19 +0,0 @@
Change: Replace `--repo2` option used by `init`/`copy` with `--from-repo`
The `init` and `copy` commands can read data from another repository.
However, confusingly `--repo2` referred to the repository *from* which the
`init` command copies parameters, but for the `copy` command `--repo2`
referred to the copy *destination*.
We've introduced a new option, `--from-repo`, which always refers to the
source repository for both commands. The old parameter names have been
deprecated but still work. To create a new repository and copy all snapshots
to it, the commands are now as follows:
```
restic -r /srv/restic-repo-copy init --from-repo /srv/restic-repo --copy-chunker-params
restic -r /srv/restic-repo-copy copy --from-repo /srv/restic-repo
```
https://github.com/restic/restic/pull/3742
https://forum.restic.net/t/5017

View File

@@ -1,14 +0,0 @@
Bugfix: Correctly rebuild index for legacy repositories
After running `rebuild-index` on a legacy repository containing mixed pack
files (that is, pack files which store both metadata and file data), `check`
printed warnings like `pack 12345678 contained in several indexes: ...`.
This warning was not critical, but has now nonetheless been fixed by properly
handling mixed pack files while rebuilding the index.
Running `prune` for such legacy repositories will also fix the warning by
reorganizing the pack files which caused it.
https://github.com/restic/restic/pull/3772
https://github.com/restic/restic/pull/3884
https://forum.restic.net/t/5044/13

View File

@@ -1,7 +0,0 @@
Enhancement: Optimize memory usage for directories with many files
Backing up a directory with hundreds of thousands or more files caused restic
to require large amounts of memory. We've now optimized the `backup` command
such that it requires up to 30% less memory.
https://github.com/restic/restic/pull/3773

View File

@@ -1,11 +0,0 @@
Bugfix: Limit number of key files tested while opening a repository
Previously, restic tested the password against every key in the repository
when opening a repository. The more keys there were in the repository, the
slower this operation became.
Restic now tests the password against up to 20 key files in the repository.
Alternatively, you can use the `--key-hint=<key ID>` option to specify a
specific key file to use instead.
https://github.com/restic/restic/pull/3776

View File

@@ -1,11 +0,0 @@
Enhancement: Validate include/exclude patterns before restoring
Patterns provided to `restore` via `--exclude`, `--iexclude`, `--include`
and `--iinclude` weren't validated before running the restore. Invalid
patterns would result in error messages being printed repeatedly, and
possibly unwanted files being restored.
Restic now validates all patterns before running the restore, and aborts
with a fatal error if an invalid pattern is detected.
https://github.com/restic/restic/pull/3819

View File

@@ -1,5 +1,5 @@
# The first line must start with Bugfix:, Enhancement: or Change:,
# including the colon. Use present tense. Remove lines starting with '#'
# including the colon. Use present use. Remove lines starting with '#'
# from this template.
Enhancement: Allow custom bar in the foo command

View File

@@ -12,16 +12,14 @@ import (
"path/filepath"
"runtime"
"strings"
"sync"
"time"
"github.com/spf13/cobra"
"golang.org/x/sync/errgroup"
tomb "gopkg.in/tomb.v2"
"github.com/restic/restic/internal/archiver"
"github.com/restic/restic/internal/debug"
"github.com/restic/restic/internal/errors"
"github.com/restic/restic/internal/filter"
"github.com/restic/restic/internal/fs"
"github.com/restic/restic/internal/repository"
"github.com/restic/restic/internal/restic"
@@ -31,7 +29,7 @@ import (
)
var cmdBackup = &cobra.Command{
Use: "backup [flags] [FILE/DIR] ...",
Use: "backup [flags] FILE/DIR [FILE/DIR] ...",
Short: "Create a new backup of files and/or directories",
Long: `
The "backup" command creates a new snapshot and saves the files and directories
@@ -56,22 +54,16 @@ Exit status is 3 if some source data could not be read (incomplete snapshot crea
},
DisableAutoGenTag: true,
RunE: func(cmd *cobra.Command, args []string) error {
var wg sync.WaitGroup
cancelCtx, cancel := context.WithCancel(globalOptions.ctx)
defer func() {
// shutdown termstatus
cancel()
wg.Wait()
}()
var t tomb.Tomb
term := termstatus.New(globalOptions.stdout, globalOptions.stderr, globalOptions.Quiet)
wg.Add(1)
go func() {
defer wg.Done()
term.Run(cancelCtx)
}()
t.Go(func() error { term.Run(t.Context(globalOptions.ctx)); return nil })
return runBackup(backupOptions, globalOptions, term, args)
err := runBackup(backupOptions, globalOptions, term, args)
t.Kill(nil)
if werr := t.Wait(); werr != nil {
panic(fmt.Sprintf("term.Run() returned err: %v", err))
}
return err
},
}
@@ -111,7 +103,7 @@ func init() {
cmdRoot.AddCommand(cmdBackup)
f := cmdBackup.Flags()
f.StringVar(&backupOptions.Parent, "parent", "", "use this parent `snapshot` (default: last snapshot in the repository that has the same target files/directories, and is not newer than the snapshot time)")
f.StringVar(&backupOptions.Parent, "parent", "", "use this parent `snapshot` (default: last snapshot in the repo that has the same target files/directories, and is not newer than the snapshot time)")
f.BoolVarP(&backupOptions.Force, "force", "f", false, `force re-reading the target files/directories (overrides the "parent" flag)`)
f.StringArrayVarP(&backupOptions.Excludes, "exclude", "e", nil, "exclude a `pattern` (can be specified multiple times)")
f.StringArrayVar(&backupOptions.InsensitiveExcludes, "iexclude", nil, "same as --exclude `pattern` but ignores the casing of filenames")
@@ -151,7 +143,7 @@ func init() {
func filterExisting(items []string) (result []string, err error) {
for _, item := range items {
_, err := fs.Lstat(item)
if errors.Is(err, os.ErrNotExist) {
if err != nil && os.IsNotExist(errors.Cause(err)) {
Warnf("%v does not exist, skipping\n", item)
continue
}
@@ -306,11 +298,6 @@ func collectRejectByNameFuncs(opts BackupOptions, repo *repository.Repository, t
if err != nil {
return nil, err
}
if valid, invalidPatterns := filter.ValidatePatterns(excludes); !valid {
return nil, errors.Fatalf("--exclude-file: invalid pattern(s) provided:\n%s", strings.Join(invalidPatterns, "\n"))
}
opts.Excludes = append(opts.Excludes, excludes...)
}
@@ -319,27 +306,14 @@ func collectRejectByNameFuncs(opts BackupOptions, repo *repository.Repository, t
if err != nil {
return nil, err
}
if valid, invalidPatterns := filter.ValidatePatterns(excludes); !valid {
return nil, errors.Fatalf("--iexclude-file: invalid pattern(s) provided:\n%s", strings.Join(invalidPatterns, "\n"))
}
opts.InsensitiveExcludes = append(opts.InsensitiveExcludes, excludes...)
}
if len(opts.InsensitiveExcludes) > 0 {
if valid, invalidPatterns := filter.ValidatePatterns(opts.InsensitiveExcludes); !valid {
return nil, errors.Fatalf("--iexclude: invalid pattern(s) provided:\n%s", strings.Join(invalidPatterns, "\n"))
}
fs = append(fs, rejectByInsensitivePattern(opts.InsensitiveExcludes))
}
if len(opts.Excludes) > 0 {
if valid, invalidPatterns := filter.ValidatePatterns(opts.Excludes); !valid {
return nil, errors.Fatalf("--exclude: invalid pattern(s) provided:\n%s", strings.Join(invalidPatterns, "\n"))
}
fs = append(fs, rejectByPattern(opts.Excludes))
}
@@ -501,7 +475,7 @@ func collectTargets(opts BackupOptions, args []string) (targets []string, err er
func findParentSnapshot(ctx context.Context, repo restic.Repository, opts BackupOptions, targets []string, timeStampLimit time.Time) (parentID *restic.ID, err error) {
// Force using a parent
if !opts.Force && opts.Parent != "" {
id, err := restic.FindSnapshot(ctx, repo.Backend(), opts.Parent)
id, err := restic.FindSnapshot(ctx, repo, opts.Parent)
if err != nil {
return nil, errors.Fatalf("invalid id %q: %v", opts.Parent, err)
}
@@ -511,7 +485,7 @@ func findParentSnapshot(ctx context.Context, repo restic.Repository, opts Backup
// Find last snapshot to set it as parent, if not already set
if !opts.Force && parentID == nil {
id, err := restic.FindLatestSnapshot(ctx, repo.Backend(), repo, targets, []restic.TagList{}, []string{opts.Host}, &timeStampLimit)
id, err := restic.FindLatestSnapshot(ctx, repo, targets, []restic.TagList{}, []string{opts.Host}, &timeStampLimit)
if err == nil {
parentID = &id
} else if err != restic.ErrNoSnapshotFound {
@@ -541,6 +515,8 @@ func runBackup(opts BackupOptions, gopts GlobalOptions, term *termstatus.Termina
}
}
var t tomb.Tomb
if gopts.verbosity >= 2 && !gopts.JSON {
Verbosef("open repository\n")
}
@@ -572,10 +548,7 @@ func runBackup(opts BackupOptions, gopts GlobalOptions, term *termstatus.Termina
progressReporter.SetMinUpdatePause(calculateProgressInterval(!gopts.Quiet, gopts.JSON))
wg, wgCtx := errgroup.WithContext(gopts.ctx)
cancelCtx, cancel := context.WithCancel(wgCtx)
defer cancel()
wg.Go(func() error { return progressReporter.Run(cancelCtx) })
t.Go(func() error { return progressReporter.Run(t.Context(gopts.ctx)) })
if !gopts.JSON {
progressPrinter.V("lock repository")
@@ -598,6 +571,14 @@ func runBackup(opts BackupOptions, gopts GlobalOptions, term *termstatus.Termina
return err
}
if !gopts.JSON {
progressPrinter.V("load index files")
}
err = repo.LoadIndex(gopts.ctx)
if err != nil {
return err
}
var parentSnapshotID *restic.ID
if !opts.Stdin {
parentSnapshotID, err = findParentSnapshot(gopts.ctx, repo, opts, targets, timeStamp)
@@ -614,14 +595,6 @@ func runBackup(opts BackupOptions, gopts GlobalOptions, term *termstatus.Termina
}
}
if !gopts.JSON {
progressPrinter.V("load index files")
}
err = repo.LoadIndex(gopts.ctx)
if err != nil {
return err
}
selectByNameFilter := func(item string) bool {
for _, reject := range rejectByNameFuncs {
if reject(item) {
@@ -647,7 +620,7 @@ func runBackup(opts BackupOptions, gopts GlobalOptions, term *termstatus.Termina
}
errorHandler := func(item string, err error) error {
return progressReporter.Error(item, err)
return progressReporter.Error(item, nil, err)
}
messageHandler := func(msg string, args ...interface{}) {
@@ -683,16 +656,16 @@ func runBackup(opts BackupOptions, gopts GlobalOptions, term *termstatus.Termina
if !gopts.JSON {
progressPrinter.V("start scan on %v", targets)
}
wg.Go(func() error { return sc.Scan(cancelCtx, targets) })
t.Go(func() error { return sc.Scan(t.Context(gopts.ctx), targets) })
arch := archiver.New(repo, targetFS, archiver.Options{})
arch.SelectByName = selectByNameFilter
arch.Select = selectFilter
arch.WithAtime = opts.WithAtime
success := true
arch.Error = func(item string, err error) error {
arch.Error = func(item string, fi os.FileInfo, err error) error {
success = false
return progressReporter.Error(item, err)
return progressReporter.Error(item, fi, err)
}
arch.CompleteItem = progressReporter.CompleteItem
arch.StartFile = progressReporter.StartFile
@@ -725,10 +698,10 @@ func runBackup(opts BackupOptions, gopts GlobalOptions, term *termstatus.Termina
_, id, err := arch.Snapshot(gopts.ctx, targets, snapshotOpts)
// cleanly shutdown all running goroutines
cancel()
t.Kill(nil)
// let's see if one returned an error
werr := wg.Wait()
werr := t.Wait()
// return original error
if err != nil {

View File

@@ -62,7 +62,7 @@ func runCat(gopts GlobalOptions, args []string) error {
}
// find snapshot id with prefix
id, err = restic.FindSnapshot(gopts.ctx, repo.Backend(), args[1])
id, err = restic.FindSnapshot(gopts.ctx, repo, args[1])
if err != nil {
return errors.Fatalf("could not find snapshot: %v\n", err)
}
@@ -79,7 +79,7 @@ func runCat(gopts GlobalOptions, args []string) error {
Println(string(buf))
return nil
case "index":
buf, err := repo.LoadUnpacked(gopts.ctx, restic.IndexFile, id, nil)
buf, err := repo.LoadAndDecrypt(gopts.ctx, nil, restic.IndexFile, id)
if err != nil {
return err
}
@@ -87,12 +87,13 @@ func runCat(gopts GlobalOptions, args []string) error {
Println(string(buf))
return nil
case "snapshot":
sn, err := restic.LoadSnapshot(gopts.ctx, repo, id)
sn := &restic.Snapshot{}
err = repo.LoadJSONUnpacked(gopts.ctx, restic.SnapshotFile, id, sn)
if err != nil {
return err
}
buf, err := json.MarshalIndent(sn, "", " ")
buf, err := json.MarshalIndent(&sn, "", " ")
if err != nil {
return err
}

View File

@@ -57,13 +57,7 @@ func init() {
f := cmdCheck.Flags()
f.BoolVar(&checkOptions.ReadData, "read-data", false, "read all data blobs")
f.StringVar(&checkOptions.ReadDataSubset, "read-data-subset", "", "read a `subset` of data packs, specified as 'n/t' for specific part, or either 'x%' or 'x.y%' or a size in bytes with suffixes k/K, m/M, g/G, t/T for a random subset")
var ignored bool
f.BoolVar(&ignored, "check-unused", false, "find unused blobs")
err := f.MarkDeprecated("check-unused", "`--check-unused` is deprecated and will be ignored")
if err != nil {
// MarkDeprecated only returns an error when the flag is not found
panic(err)
}
f.BoolVar(&checkOptions.CheckUnused, "check-unused", false, "find unused blobs")
f.BoolVar(&checkOptions.WithCache, "with-cache", false, "use the cache")
}
@@ -148,10 +142,10 @@ func parsePercentage(s string) (float64, error) {
// prepareCheckCache configures a special cache directory for check.
//
// - if --with-cache is specified, the default cache is used
// - if the user explicitly requested --no-cache, we don't use any cache
// - if the user provides --cache-dir, we use a cache in a temporary sub-directory of the specified directory and the sub-directory is deleted after the check
// - by default, we use a cache in a temporary directory that is deleted after the check
// * if --with-cache is specified, the default cache is used
// * if the user explicitly requested --no-cache, we don't use any cache
// * if the user provides --cache-dir, we use a cache in a temporary sub-directory of the specified directory and the sub-directory is deleted after the check
// * by default, we use a cache in a temporary directory that is deleted after the check
func prepareCheckCache(opts CheckOptions, gopts *GlobalOptions) (cleanup func()) {
cleanup = func() {}
if opts.WithCache {
@@ -217,37 +211,21 @@ func runCheck(opts CheckOptions, gopts GlobalOptions, args []string) error {
}
chkr := checker.New(repo, opts.CheckUnused)
err = chkr.LoadSnapshots(gopts.ctx)
if err != nil {
return err
}
Verbosef("load indexes\n")
hints, errs := chkr.LoadIndex(gopts.ctx)
errorsFound := false
suggestIndexRebuild := false
mixedFound := false
dupFound := false
for _, hint := range hints {
switch hint.(type) {
case *checker.ErrDuplicatePacks, *checker.ErrOldIndexFormat:
Printf("%v\n", hint)
suggestIndexRebuild = true
case *checker.ErrMixedPack:
Printf("%v\n", hint)
mixedFound = true
default:
Warnf("error: %v\n", hint)
errorsFound = true
Printf("%v\n", hint)
if _, ok := hint.(checker.ErrDuplicatePacks); ok {
dupFound = true
}
}
if suggestIndexRebuild {
if dupFound {
Printf("This is non-critical, you can run `restic rebuild-index' to correct this\n")
}
if mixedFound {
Printf("Mixed packs with tree and data blobs are non-critical, you can run `restic prune` to correct this.\n")
}
if len(errs) > 0 {
for _, err := range errs {
@@ -256,6 +234,7 @@ func runCheck(opts CheckOptions, gopts GlobalOptions, args []string) error {
return errors.Fatal("LoadIndex returned errors")
}
errorsFound := false
orphanedPacks := 0
errChan := make(chan error)
@@ -266,16 +245,14 @@ func runCheck(opts CheckOptions, gopts GlobalOptions, args []string) error {
if checker.IsOrphanedPack(err) {
orphanedPacks++
Verbosef("%v\n", err)
} else if _, ok := err.(*checker.ErrLegacyLayout); ok {
Verbosef("repository still uses the S3 legacy layout\nPlease run `restic migrate s3legacy` to correct this.\n")
} else {
errorsFound = true
Warnf("%v\n", err)
continue
}
errorsFound = true
Warnf("%v\n", err)
}
if orphanedPacks > 0 {
Verbosef("%d additional files were found in the repo, which likely contain duplicate data.\nThis is non-critical, you can run `restic prune` to correct this.\n", orphanedPacks)
Verbosef("%d additional files were found in the repo, which likely contain duplicate data.\nYou can run `restic prune` to correct this.\n", orphanedPacks)
}
Verbosef("check snapshots, trees and blobs\n")
@@ -292,7 +269,7 @@ func runCheck(opts CheckOptions, gopts GlobalOptions, args []string) error {
for err := range errChan {
errorsFound = true
if e, ok := err.(*checker.TreeError); ok {
if e, ok := err.(checker.TreeError); ok {
Warnf("error for tree %v:\n", e.ID.Str())
for _, treeErr := range e.Errors {
Warnf(" %v\n", treeErr)

View File

@@ -4,9 +4,7 @@ import (
"context"
"fmt"
"github.com/restic/restic/internal/backend"
"github.com/restic/restic/internal/debug"
"github.com/restic/restic/internal/repository"
"github.com/restic/restic/internal/restic"
"golang.org/x/sync/errgroup"
@@ -50,21 +48,17 @@ func init() {
cmdRoot.AddCommand(cmdCopy)
f := cmdCopy.Flags()
initSecondaryRepoOptions(f, &copyOptions.secondaryRepoOptions, "destination", "to copy snapshots from")
initSecondaryRepoOptions(f, &copyOptions.secondaryRepoOptions, "destination", "to copy snapshots to")
f.StringArrayVarP(&copyOptions.Hosts, "host", "H", nil, "only consider snapshots for this `host`, when no snapshot ID is given (can be specified multiple times)")
f.Var(&copyOptions.Tags, "tag", "only consider snapshots which include this `taglist`, when no snapshot ID is given")
f.StringArrayVar(&copyOptions.Paths, "path", nil, "only consider snapshots which include this (absolute) `path`, when no snapshot ID is given")
}
func runCopy(opts CopyOptions, gopts GlobalOptions, args []string) error {
secondaryGopts, isFromRepo, err := fillSecondaryGlobalOpts(opts.secondaryRepoOptions, gopts, "destination")
dstGopts, err := fillSecondaryGlobalOpts(opts.secondaryRepoOptions, gopts, "destination")
if err != nil {
return err
}
if isFromRepo {
// swap global options, if the secondary repo was set via from-repo
gopts, secondaryGopts = secondaryGopts, gopts
}
ctx, cancel := context.WithCancel(gopts.ctx)
defer cancel()
@@ -74,7 +68,7 @@ func runCopy(opts CopyOptions, gopts GlobalOptions, args []string) error {
return err
}
dstRepo, err := OpenRepository(secondaryGopts)
dstRepo, err := OpenRepository(dstGopts)
if err != nil {
return err
}
@@ -93,16 +87,6 @@ func runCopy(opts CopyOptions, gopts GlobalOptions, args []string) error {
return err
}
srcSnapshotLister, err := backend.MemorizeList(ctx, srcRepo.Backend(), restic.SnapshotFile)
if err != nil {
return err
}
dstSnapshotLister, err := backend.MemorizeList(ctx, dstRepo.Backend(), restic.SnapshotFile)
if err != nil {
return err
}
debug.Log("Loading source index")
if err := srcRepo.LoadIndex(ctx); err != nil {
return err
@@ -114,7 +98,7 @@ func runCopy(opts CopyOptions, gopts GlobalOptions, args []string) error {
}
dstSnapshotByOriginal := make(map[restic.ID][]*restic.Snapshot)
for sn := range FindFilteredSnapshots(ctx, dstSnapshotLister, dstRepo, opts.Hosts, opts.Tags, opts.Paths, nil) {
for sn := range FindFilteredSnapshots(ctx, dstRepo, opts.Hosts, opts.Tags, opts.Paths, nil) {
if sn.Original != nil && !sn.Original.IsNull() {
dstSnapshotByOriginal[*sn.Original] = append(dstSnapshotByOriginal[*sn.Original], sn)
}
@@ -125,7 +109,7 @@ func runCopy(opts CopyOptions, gopts GlobalOptions, args []string) error {
// remember already processed trees across all snapshots
visitedTrees := restic.NewIDSet()
for sn := range FindFilteredSnapshots(ctx, srcSnapshotLister, srcRepo, opts.Hosts, opts.Tags, opts.Paths, args) {
for sn := range FindFilteredSnapshots(ctx, srcRepo, opts.Hosts, opts.Tags, opts.Paths, args) {
Verbosef("\nsnapshot %s of %v at %s)\n", sn.ID().Str(), sn.Paths, sn.Time)
// check whether the destination has a snapshot with the same persistent ID which has similar snapshot fields
@@ -147,18 +131,24 @@ func runCopy(opts CopyOptions, gopts GlobalOptions, args []string) error {
}
}
Verbosef(" copy started, this may take a while...\n")
if err := copyTree(ctx, srcRepo, dstRepo, visitedTrees, *sn.Tree, gopts.Quiet); err != nil {
if err := copyTree(ctx, srcRepo, dstRepo, visitedTrees, *sn.Tree); err != nil {
return err
}
debug.Log("tree copied")
if err = dstRepo.Flush(ctx); err != nil {
return err
}
debug.Log("flushed packs and saved index")
// save snapshot
sn.Parent = nil // Parent does not have relevance in the new repo.
// Use Original as a persistent snapshot ID
if sn.Original == nil {
sn.Original = sn.ID()
}
newID, err := restic.SaveSnapshot(ctx, dstRepo, sn)
newID, err := dstRepo.SaveJSONUnpacked(ctx, restic.SnapshotFile, sn)
if err != nil {
return err
}
@@ -186,61 +176,82 @@ func similarSnapshots(sna *restic.Snapshot, snb *restic.Snapshot) bool {
return true
}
const numCopyWorkers = 8
func copyTree(ctx context.Context, srcRepo restic.Repository, dstRepo restic.Repository,
visitedTrees restic.IDSet, rootTreeID restic.ID, quiet bool) error {
visitedTrees restic.IDSet, rootTreeID restic.ID) error {
wg, wgCtx := errgroup.WithContext(ctx)
idChan := make(chan restic.ID)
wg, ctx := errgroup.WithContext(ctx)
treeStream := restic.StreamTrees(wgCtx, wg, srcRepo, restic.IDs{rootTreeID}, func(treeID restic.ID) bool {
treeStream := restic.StreamTrees(ctx, wg, srcRepo, restic.IDs{rootTreeID}, func(treeID restic.ID) bool {
visited := visitedTrees.Has(treeID)
visitedTrees.Insert(treeID)
return visited
}, nil)
copyBlobs := restic.NewBlobSet()
packList := restic.NewIDSet()
enqueue := func(h restic.BlobHandle) {
pb := srcRepo.Index().Lookup(h)
copyBlobs.Insert(h)
for _, p := range pb {
packList.Insert(p.PackID)
}
}
wg.Go(func() error {
defer close(idChan)
// reused buffer
var buf []byte
for tree := range treeStream {
if tree.Error != nil {
return fmt.Errorf("LoadTree(%v) returned error %v", tree.ID.Str(), tree.Error)
}
// Do we already have this tree blob?
treeHandle := restic.BlobHandle{ID: tree.ID, Type: restic.TreeBlob}
if !dstRepo.Index().Has(treeHandle) {
if !dstRepo.Index().Has(restic.BlobHandle{ID: tree.ID, Type: restic.TreeBlob}) {
// copy raw tree bytes to avoid problems if the serialization changes
enqueue(treeHandle)
var err error
buf, err = srcRepo.LoadBlob(ctx, restic.TreeBlob, tree.ID, buf)
if err != nil {
return fmt.Errorf("LoadBlob(%v) for tree returned error %v", tree.ID, err)
}
_, _, err = dstRepo.SaveBlob(ctx, restic.TreeBlob, buf, tree.ID, false)
if err != nil {
return fmt.Errorf("SaveBlob(%v) for tree returned error %v", tree.ID.Str(), err)
}
}
for _, entry := range tree.Nodes {
// Recursion into directories is handled by StreamTrees
// Copy the blobs for this file.
for _, blobID := range entry.Content {
h := restic.BlobHandle{Type: restic.DataBlob, ID: blobID}
if !dstRepo.Index().Has(h) {
enqueue(h)
select {
case idChan <- blobID:
case <-ctx.Done():
return ctx.Err()
}
}
}
}
return nil
})
err := wg.Wait()
if err != nil {
return err
}
bar := newProgressMax(!quiet, uint64(len(packList)), "packs copied")
_, err = repository.Repack(ctx, srcRepo, dstRepo, packList, copyBlobs, bar)
bar.Done()
return err
for i := 0; i < numCopyWorkers; i++ {
wg.Go(func() error {
// reused buffer
var buf []byte
for blobID := range idChan {
// Do we already have this data blob?
if dstRepo.Index().Has(restic.BlobHandle{ID: blobID, Type: restic.DataBlob}) {
continue
}
debug.Log("Copying blob %s\n", blobID.Str())
var err error
buf, err = srcRepo.LoadBlob(ctx, restic.DataBlob, blobID, buf)
if err != nil {
return fmt.Errorf("LoadBlob(%v) returned error %v", blobID, err)
}
_, _, err = dstRepo.SaveBlob(ctx, restic.DataBlob, buf, blobID, false)
if err != nil {
return fmt.Errorf("SaveBlob(%v) returned error %v", blobID, err)
}
}
return nil
})
}
return wg.Wait()
}

View File

@@ -1,4 +1,3 @@
//go:build debug
// +build debug
package main
@@ -15,7 +14,6 @@ import (
"sort"
"time"
"github.com/klauspost/compress/zstd"
"github.com/spf13/cobra"
"golang.org/x/sync/errgroup"
@@ -53,14 +51,12 @@ Exit status is 0 if the command was successful, and non-zero if there was any er
var tryRepair bool
var repairByte bool
var extractPack bool
var reuploadBlobs bool
func init() {
cmdRoot.AddCommand(cmdDebug)
cmdDebug.AddCommand(cmdDebugDump)
cmdDebug.AddCommand(cmdDebugExamine)
cmdDebugExamine.Flags().BoolVar(&extractPack, "extract-pack", false, "write blobs to the current directory")
cmdDebugExamine.Flags().BoolVar(&reuploadBlobs, "reupload-blobs", false, "reupload blobs to the repository")
cmdDebugExamine.Flags().BoolVar(&tryRepair, "try-repair", false, "try to repair broken blobs with single bit flips")
cmdDebugExamine.Flags().BoolVar(&repairByte, "repair-byte", false, "try to repair broken blobs by trying bytes")
}
@@ -76,7 +72,7 @@ func prettyPrintJSON(wr io.Writer, item interface{}) error {
}
func debugPrintSnapshots(ctx context.Context, repo *repository.Repository, wr io.Writer) error {
return restic.ForAllSnapshots(ctx, repo.Backend(), repo, nil, func(id restic.ID, snapshot *restic.Snapshot, err error) error {
return restic.ForAllSnapshots(ctx, repo, nil, func(id restic.ID, snapshot *restic.Snapshot, err error) error {
if err != nil {
return err
}
@@ -107,7 +103,7 @@ func printPacks(ctx context.Context, repo *repository.Repository, wr io.Writer)
return repo.List(ctx, restic.PackFile, func(id restic.ID, size int64) error {
h := restic.Handle{Type: restic.PackFile, Name: id.String()}
blobs, _, err := pack.List(repo.Key(), backend.ReaderAt(ctx, repo.Backend(), h), size)
blobs, _, err := pack.List(repo.Key(), restic.ReaderAt(ctx, repo.Backend(), h), size)
if err != nil {
Warnf("error for pack %v: %v\n", id.Str(), err)
return nil
@@ -312,10 +308,6 @@ func decryptUnsigned(ctx context.Context, k *crypto.Key, buf []byte) []byte {
}
func loadBlobs(ctx context.Context, repo restic.Repository, pack restic.ID, list []restic.Blob) error {
dec, err := zstd.NewReader(nil)
if err != nil {
panic(err)
}
be := repo.Backend()
h := restic.Handle{
Name: pack.String(),
@@ -340,65 +332,48 @@ func loadBlobs(ctx context.Context, repo restic.Repository, pack restic.ID, list
nonce, plaintext := buf[:key.NonceSize()], buf[key.NonceSize():]
plaintext, err = key.Open(plaintext[:0], nonce, plaintext, nil)
outputPrefix := ""
filePrefix := ""
if err != nil {
Warnf("error decrypting blob: %v\n", err)
var plain []byte
if tryRepair || repairByte {
plaintext = tryRepairWithBitflip(ctx, key, buf, repairByte)
plain = tryRepairWithBitflip(ctx, key, buf, repairByte)
}
if plaintext != nil {
outputPrefix = "repaired "
filePrefix = "repaired-"
} else {
plaintext = decryptUnsigned(ctx, key, buf)
err = storePlainBlob(blob.ID, "damaged-", plaintext)
if err != nil {
return err
var prefix string
if plain != nil {
id := restic.Hash(plain)
if !id.Equal(blob.ID) {
Printf(" repaired blob (length %v), hash is %v, ID does not match, wanted %v\n", len(plain), id, blob.ID)
prefix = "repaired-wrong-hash-"
} else {
Printf(" successfully repaired blob (length %v), hash is %v, ID matches\n", len(plain), id)
prefix = "repaired-"
}
continue
} else {
plain = decryptUnsigned(ctx, key, buf)
prefix = "damaged-"
}
}
if blob.IsCompressed() {
decompressed, err := dec.DecodeAll(plaintext, nil)
err = storePlainBlob(blob.ID, prefix, plain)
if err != nil {
Printf(" failed to decompress blob %v\n", blob.ID)
}
if decompressed != nil {
plaintext = decompressed
return err
}
continue
}
id := restic.Hash(plaintext)
var prefix string
if !id.Equal(blob.ID) {
Printf(" successfully %vdecrypted blob (length %v), hash is %v, ID does not match, wanted %v\n", outputPrefix, len(plaintext), id, blob.ID)
Printf(" successfully decrypted blob (length %v), hash is %v, ID does not match, wanted %v\n", len(plaintext), id, blob.ID)
prefix = "wrong-hash-"
} else {
Printf(" successfully %vdecrypted blob (length %v), hash is %v, ID matches\n", outputPrefix, len(plaintext), id)
Printf(" successfully decrypted blob (length %v), hash is %v, ID matches\n", len(plaintext), id)
prefix = "correct-"
}
if extractPack {
err = storePlainBlob(id, filePrefix+prefix, plaintext)
err = storePlainBlob(id, prefix, plaintext)
if err != nil {
return err
}
}
if reuploadBlobs {
_, _, _, err := repo.SaveBlob(ctx, blob.Type, plaintext, id, true)
if err != nil {
return err
}
Printf(" uploaded %v %v\n", blob.Type, id)
}
}
if reuploadBlobs {
err := repo.Flush(ctx)
if err != nil {
return err
}
}
return nil
@@ -427,23 +402,12 @@ func storePlainBlob(id restic.ID, prefix string, plain []byte) error {
}
func runDebugExamine(gopts GlobalOptions, args []string) error {
repo, err := OpenRepository(gopts)
if err != nil {
return err
}
ids := make([]restic.ID, 0)
for _, name := range args {
id, err := restic.ParseID(name)
if err != nil {
name, err = restic.Find(gopts.ctx, repo.Backend(), restic.PackFile, name)
if err == nil {
id, err = restic.ParseID(name)
}
if err != nil {
Warnf("error: %v\n", err)
continue
}
Warnf("error: %v\n", err)
continue
}
ids = append(ids, id)
}
@@ -452,6 +416,11 @@ func runDebugExamine(gopts GlobalOptions, args []string) error {
return errors.Fatal("no pack files to examine")
}
repo, err := OpenRepository(gopts)
if err != nil {
return err
}
if !gopts.NoLock {
lock, err := lockRepo(gopts.ctx, repo)
defer unlockRepo(lock)
@@ -506,15 +475,27 @@ func examinePack(ctx context.Context, repo restic.Repository, id restic.ID) erro
blobsLoaded := false
// examine all data the indexes have for the pack file
for b := range repo.Index().ListPacks(ctx, restic.NewIDSet(id)) {
blobs := b.Blobs
for _, idx := range repo.Index().(*repository.MasterIndex).All() {
idxIDs, err := idx.IDs()
if err != nil {
idxIDs = restic.IDs{}
}
blobs := idx.ListPack(id)
if len(blobs) == 0 {
continue
}
checkPackSize(blobs, fi.Size)
Printf(" index %v:\n", idxIDs)
err = loadBlobs(ctx, repo, id, blobs)
// convert list of blobs to []restic.Blob
var list []restic.Blob
for _, b := range blobs {
list = append(list, b.Blob)
}
checkPackSize(list, fi.Size)
err = loadBlobs(ctx, repo, id, list)
if err != nil {
Warnf("error: %v\n", err)
} else {
@@ -525,7 +506,7 @@ func examinePack(ctx context.Context, repo restic.Repository, id restic.ID) erro
Printf(" ========================================\n")
Printf(" inspect the pack itself\n")
blobs, _, err := pack.List(repo.Key(), backend.ReaderAt(ctx, repo.Backend(), h), fi.Size)
blobs, _, err := pack.List(repo.Key(), restic.ReaderAt(ctx, repo.Backend(), h), fi.Size)
if err != nil {
return fmt.Errorf("pack %v: %v", id.Str(), err)
}
@@ -550,10 +531,14 @@ func checkPackSize(blobs []restic.Blob, fileSize int64) {
if offset != uint64(pb.Offset) {
Printf(" hole in file, want offset %v, got %v\n", offset, pb.Offset)
}
offset = uint64(pb.Offset + pb.Length)
offset += uint64(pb.Length)
size += uint64(pb.Length)
}
size += uint64(pack.CalculateHeaderSize(blobs))
// compute header size, per blob: 1 byte type, 4 byte length, 32 byte id
size += uint64(restic.CiphertextLength(len(blobs) * (1 + 4 + 32)))
// length in uint32 little endian
size += 4
if uint64(fileSize) != size {
Printf(" file sizes do not match: computed %v from index, file size is %v\n", size, fileSize)

View File

@@ -7,9 +7,9 @@ import (
"reflect"
"sort"
"github.com/restic/restic/internal/backend"
"github.com/restic/restic/internal/debug"
"github.com/restic/restic/internal/errors"
"github.com/restic/restic/internal/repository"
"github.com/restic/restic/internal/restic"
"github.com/spf13/cobra"
)
@@ -53,8 +53,8 @@ func init() {
f.BoolVar(&diffOptions.ShowMetadata, "metadata", false, "print changes in metadata")
}
func loadSnapshot(ctx context.Context, be restic.Lister, repo restic.Repository, desc string) (*restic.Snapshot, error) {
id, err := restic.FindSnapshot(ctx, be, desc)
func loadSnapshot(ctx context.Context, repo *repository.Repository, desc string) (*restic.Snapshot, error) {
id, err := restic.FindSnapshot(ctx, repo, desc)
if err != nil {
return nil, errors.Fatal(err.Error())
}
@@ -160,7 +160,7 @@ func updateBlobs(repo restic.Repository, blobs restic.BlobSet, stats *DiffStat)
func (c *Comparer) printDir(ctx context.Context, mode string, stats *DiffStat, blobs restic.BlobSet, prefix string, id restic.ID) error {
debug.Log("print %v tree %v", mode, id)
tree, err := restic.LoadTree(ctx, c.repo, id)
tree, err := c.repo.LoadTree(ctx, id)
if err != nil {
return err
}
@@ -187,7 +187,7 @@ func (c *Comparer) printDir(ctx context.Context, mode string, stats *DiffStat, b
func (c *Comparer) collectDir(ctx context.Context, blobs restic.BlobSet, id restic.ID) error {
debug.Log("print tree %v", id)
tree, err := restic.LoadTree(ctx, c.repo, id)
tree, err := c.repo.LoadTree(ctx, id)
if err != nil {
return err
}
@@ -231,12 +231,12 @@ func uniqueNodeNames(tree1, tree2 *restic.Tree) (tree1Nodes, tree2Nodes map[stri
func (c *Comparer) diffTree(ctx context.Context, stats *DiffStatsContainer, prefix string, id1, id2 restic.ID) error {
debug.Log("diffing %v to %v", id1, id2)
tree1, err := restic.LoadTree(ctx, c.repo, id1)
tree1, err := c.repo.LoadTree(ctx, id1)
if err != nil {
return err
}
tree2, err := restic.LoadTree(ctx, c.repo, id2)
tree2, err := c.repo.LoadTree(ctx, id2)
if err != nil {
return err
}
@@ -334,6 +334,10 @@ func runDiff(opts DiffOptions, gopts GlobalOptions, args []string) error {
return err
}
if err = repo.LoadIndex(ctx); err != nil {
return err
}
if !gopts.NoLock {
lock, err := lockRepo(ctx, repo)
defer unlockRepo(lock)
@@ -342,17 +346,12 @@ func runDiff(opts DiffOptions, gopts GlobalOptions, args []string) error {
}
}
// cache snapshots listing
be, err := backend.MemorizeList(ctx, repo.Backend(), restic.SnapshotFile)
if err != nil {
return err
}
sn1, err := loadSnapshot(ctx, be, repo, args[0])
sn1, err := loadSnapshot(ctx, repo, args[0])
if err != nil {
return err
}
sn2, err := loadSnapshot(ctx, be, repo, args[1])
sn2, err := loadSnapshot(ctx, repo, args[1])
if err != nil {
return err
}
@@ -361,10 +360,6 @@ func runDiff(opts DiffOptions, gopts GlobalOptions, args []string) error {
Verbosef("comparing snapshot %v to %v:\n\n", sn1.ID().Str(), sn2.ID().Str())
}
if err = repo.LoadIndex(ctx); err != nil {
return err
}
if sn1.Tree == nil {
return errors.Errorf("snapshot %v has nil tree", sn1.ID().Str())
}

View File

@@ -87,7 +87,7 @@ func printFromTree(ctx context.Context, tree *restic.Tree, repo restic.Repositor
case l == 1 && dump.IsFile(node):
return d.WriteNode(ctx, node)
case l > 1 && dump.IsDir(node):
subtree, err := restic.LoadTree(ctx, repo, *node.Subtree)
subtree, err := repo.LoadTree(ctx, *node.Subtree)
if err != nil {
return errors.Wrapf(err, "cannot load subtree for %q", item)
}
@@ -96,7 +96,7 @@ func printFromTree(ctx context.Context, tree *restic.Tree, repo restic.Repositor
if err := checkStdoutArchive(); err != nil {
return err
}
subtree, err := restic.LoadTree(ctx, repo, *node.Subtree)
subtree, err := repo.LoadTree(ctx, *node.Subtree)
if err != nil {
return err
}
@@ -144,15 +144,20 @@ func runDump(opts DumpOptions, gopts GlobalOptions, args []string) error {
}
}
err = repo.LoadIndex(ctx)
if err != nil {
return err
}
var id restic.ID
if snapshotIDString == "latest" {
id, err = restic.FindLatestSnapshot(ctx, repo.Backend(), repo, opts.Paths, opts.Tags, opts.Hosts, nil)
id, err = restic.FindLatestSnapshot(ctx, repo, opts.Paths, opts.Tags, opts.Hosts, nil)
if err != nil {
Exitf(1, "latest snapshot for criteria not found: %v Paths:%v Hosts:%v", err, opts.Paths, opts.Hosts)
}
} else {
id, err = restic.FindSnapshot(ctx, repo.Backend(), snapshotIDString)
id, err = restic.FindSnapshot(ctx, repo, snapshotIDString)
if err != nil {
Exitf(1, "invalid id %q: %v", snapshotIDString, err)
}
@@ -163,12 +168,7 @@ func runDump(opts DumpOptions, gopts GlobalOptions, args []string) error {
Exitf(2, "loading snapshot %q failed: %v", snapshotIDString, err)
}
err = repo.LoadIndex(ctx)
if err != nil {
return err
}
tree, err := restic.LoadTree(ctx, repo, *sn.Tree)
tree, err := repo.LoadTree(ctx, *sn.Tree)
if err != nil {
Exitf(2, "loading tree for snapshot %q failed: %v", snapshotIDString, err)
}

View File

@@ -9,7 +9,6 @@ import (
"github.com/spf13/cobra"
"github.com/restic/restic/internal/backend"
"github.com/restic/restic/internal/debug"
"github.com/restic/restic/internal/errors"
"github.com/restic/restic/internal/filter"
@@ -585,11 +584,6 @@ func runFind(opts FindOptions, gopts GlobalOptions, args []string) error {
}
}
snapshotLister, err := backend.MemorizeList(gopts.ctx, repo.Backend(), restic.SnapshotFile)
if err != nil {
return err
}
if err = repo.LoadIndex(gopts.ctx); err != nil {
return err
}
@@ -624,7 +618,7 @@ func runFind(opts FindOptions, gopts GlobalOptions, args []string) error {
}
}
for sn := range FindFilteredSnapshots(ctx, snapshotLister, repo, opts.Hosts, opts.Tags, opts.Paths, opts.Snapshots) {
for sn := range FindFilteredSnapshots(ctx, repo, opts.Hosts, opts.Tags, opts.Paths, opts.Snapshots) {
if f.blobIDs != nil || f.treeIDs != nil {
if err = f.findIDs(ctx, sn); err != nil && err.Error() != "OK" {
return err

View File

@@ -14,16 +14,11 @@ var cmdForget = &cobra.Command{
Use: "forget [flags] [snapshot ID] [...]",
Short: "Remove snapshots from the repository",
Long: `
The "forget" command removes snapshots according to a policy. All snapshots are
first divided into groups according to "--group-by", and after that the policy
specified by the "--keep-*" options is applied to each group individually.
Please note that this command really only deletes the snapshot object in the
repository, which is a reference to data stored there. In order to remove the
unreferenced data after "forget" was run successfully, see the "prune" command.
Please also read the documentation for "forget" to learn about some important
security considerations.
The "forget" command removes snapshots according to a policy. Please note that
this command really only deletes the snapshot object in the repository, which
is a reference to data stored there. In order to remove the unreferenced data
after "forget" was run successfully, see the "prune" command. Please also read
the documentation for "forget" to learn about important security considerations.
EXIT STATUS
===========
@@ -96,7 +91,7 @@ func init() {
f.StringArrayVar(&forgetOptions.Paths, "path", nil, "only consider snapshots which include this (absolute) `path` (can be specified multiple times)")
f.BoolVarP(&forgetOptions.Compact, "compact", "c", false, "use compact output format")
f.StringVarP(&forgetOptions.GroupBy, "group-by", "g", "host,paths", "`group` snapshots by host, paths and/or tags, separated by comma (disable grouping with '')")
f.StringVarP(&forgetOptions.GroupBy, "group-by", "g", "host,paths", "string for grouping snapshots by host,paths,tags")
f.BoolVarP(&forgetOptions.DryRun, "dry-run", "n", false, "do not delete anything, just print what would be done")
f.BoolVar(&forgetOptions.Prune, "prune", false, "automatically run the 'prune' command if snapshots have been removed")
@@ -133,7 +128,7 @@ func runForget(opts ForgetOptions, gopts GlobalOptions, args []string) error {
var snapshots restic.Snapshots
removeSnIDs := restic.NewIDSet()
for sn := range FindFilteredSnapshots(ctx, repo.Backend(), repo, opts.Hosts, opts.Tags, opts.Paths, args) {
for sn := range FindFilteredSnapshots(ctx, repo, opts.Hosts, opts.Tags, opts.Paths, args) {
snapshots = append(snapshots, sn)
}

View File

@@ -1,13 +1,10 @@
package main
import (
"strconv"
"github.com/restic/chunker"
"github.com/restic/restic/internal/backend/location"
"github.com/restic/restic/internal/errors"
"github.com/restic/restic/internal/repository"
"github.com/restic/restic/internal/restic"
"github.com/spf13/cobra"
)
@@ -33,7 +30,6 @@ Exit status is 0 if the command was successful, and non-zero if there was any er
type InitOptions struct {
secondaryRepoOptions
CopyChunkerParameters bool
RepositoryVersion string
}
var initOptions InitOptions
@@ -44,26 +40,9 @@ func init() {
f := cmdInit.Flags()
initSecondaryRepoOptions(f, &initOptions.secondaryRepoOptions, "secondary", "to copy chunker parameters from")
f.BoolVar(&initOptions.CopyChunkerParameters, "copy-chunker-params", false, "copy chunker parameters from the secondary repository (useful with the copy command)")
f.StringVar(&initOptions.RepositoryVersion, "repository-version", "stable", "repository format version to use, allowed values are a format version, 'latest' and 'stable'")
}
func runInit(opts InitOptions, gopts GlobalOptions, args []string) error {
var version uint
if opts.RepositoryVersion == "latest" || opts.RepositoryVersion == "" {
version = restic.MaxRepoVersion
} else if opts.RepositoryVersion == "stable" {
version = restic.StableRepoVersion
} else {
v, err := strconv.ParseUint(opts.RepositoryVersion, 10, 32)
if err != nil {
return errors.Fatal("invalid repository version")
}
version = uint(v)
}
if version < restic.MinRepoVersion || version > restic.MaxRepoVersion {
return errors.Fatalf("only repository versions between %v and %v are allowed", restic.MinRepoVersion, restic.MaxRepoVersion)
}
chunkerPolynomial, err := maybeReadChunkerPolynomial(opts, gopts)
if err != nil {
return err
@@ -86,15 +65,9 @@ func runInit(opts InitOptions, gopts GlobalOptions, args []string) error {
return errors.Fatalf("create repository at %s failed: %v\n", location.StripPassword(gopts.Repo), err)
}
s, err := repository.New(be, repository.Options{
Compression: gopts.Compression,
PackSize: gopts.PackSize * 1024 * 1024,
})
if err != nil {
return err
}
s := repository.New(be)
err = s.Init(gopts.ctx, version, gopts.password, chunkerPolynomial)
err = s.Init(gopts.ctx, gopts.password, chunkerPolynomial)
if err != nil {
return errors.Fatalf("create key in repository at %s failed: %v\n", location.StripPassword(gopts.Repo), err)
}
@@ -110,7 +83,7 @@ func runInit(opts InitOptions, gopts GlobalOptions, args []string) error {
func maybeReadChunkerPolynomial(opts InitOptions, gopts GlobalOptions) (*chunker.Pol, error) {
if opts.CopyChunkerParameters {
otherGopts, _, err := fillSecondaryGlobalOpts(opts.secondaryRepoOptions, gopts, "secondary")
otherGopts, err := fillSecondaryGlobalOpts(opts.secondaryRepoOptions, gopts, "secondary")
if err != nil {
return nil, err
}
@@ -124,7 +97,7 @@ func maybeReadChunkerPolynomial(opts InitOptions, gopts GlobalOptions) (*chunker
return &pol, nil
}
if opts.Repo != "" || opts.RepositoryFile != "" || opts.LegacyRepo != "" || opts.LegacyRepositoryFile != "" {
if opts.Repo != "" {
return nil, errors.Fatal("Secondary repository must only be specified when copying the chunker parameters")
}
return nil, nil

View File

@@ -9,7 +9,6 @@ import (
"github.com/spf13/cobra"
"github.com/restic/restic/internal/backend"
"github.com/restic/restic/internal/errors"
"github.com/restic/restic/internal/fs"
"github.com/restic/restic/internal/restic"
@@ -170,11 +169,6 @@ func runLs(opts LsOptions, gopts GlobalOptions, args []string) error {
return err
}
snapshotLister, err := backend.MemorizeList(gopts.ctx, repo.Backend(), restic.SnapshotFile)
if err != nil {
return err
}
if err = repo.LoadIndex(gopts.ctx); err != nil {
return err
}
@@ -217,7 +211,7 @@ func runLs(opts LsOptions, gopts GlobalOptions, args []string) error {
}
}
for sn := range FindFilteredSnapshots(ctx, snapshotLister, repo, opts.Hosts, opts.Tags, opts.Paths, args[:1]) {
for sn := range FindFilteredSnapshots(ctx, repo, opts.Hosts, opts.Tags, opts.Paths, args[:1]) {
printSnapshot(sn)
err := walker.Walk(ctx, repo, *sn.Tree, nil, func(_ restic.ID, nodepath string, node *restic.Node, err error) (bool, error) {

View File

@@ -8,12 +8,11 @@ import (
)
var cmdMigrate = &cobra.Command{
Use: "migrate [flags] [migration name] [...]",
Use: "migrate [flags] [name]",
Short: "Apply migrations",
Long: `
The "migrate" command checks which migrations can be applied for a repository
and prints a list with available migration names. If one or more migration
names are specified, these migrations are applied.
The "migrate" command applies migrations to a repository. When no migration
name is explicitly given, a list of migrations that can be applied is printed.
EXIT STATUS
===========
@@ -42,8 +41,6 @@ func init() {
func checkMigrations(opts MigrateOptions, gopts GlobalOptions, repo restic.Repository) error {
ctx := gopts.ctx
Printf("available migrations:\n")
found := false
for _, m := range migrations.All {
ok, err := m.Check(ctx, repo)
if err != nil {
@@ -51,15 +48,10 @@ func checkMigrations(opts MigrateOptions, gopts GlobalOptions, repo restic.Repos
}
if ok {
Printf(" %v\t%v\n", m.Name(), m.Desc())
found = true
Printf(" %v: %v\n", m.Name(), m.Desc())
}
}
if !found {
Printf("no migrations found")
}
return nil
}
@@ -84,19 +76,6 @@ func applyMigrations(opts MigrateOptions, gopts GlobalOptions, repo restic.Repos
Warnf("check for migration %v failed, continuing anyway\n", m.Name())
}
if m.RepoCheck() {
Printf("checking repository integrity...\n")
checkOptions := CheckOptions{}
checkGopts := gopts
// the repository is already locked
checkGopts.NoLock = true
err = runCheck(checkOptions, checkGopts, []string{})
if err != nil {
return err
}
}
Printf("applying migration %v...\n", m.Name())
if err = m.Apply(ctx, repo); err != nil {
Warnf("migration %v failed: %v\n", m.Name(), err)

View File

@@ -1,4 +1,3 @@
//go:build darwin || freebsd || linux
// +build darwin freebsd linux
package main
@@ -31,13 +30,10 @@ read-only mount.
Snapshot Directories
====================
If you need a different template for directories that contain snapshots,
you can pass a time template via --time-template and path templates via
--path-template.
If you need a different template for all directories that contain snapshots,
you can pass a template via --snapshot-template. Example without colons:
Example time template without colons:
--time-template "2006-01-02_15-04-05"
--snapshot-template "2006-01-02_15-04-05"
You need to specify a sample format for exactly the following timestamp:
@@ -46,20 +42,6 @@ You need to specify a sample format for exactly the following timestamp:
For details please see the documentation for time.Format() at:
https://godoc.org/time#Time.Format
For path templates, you can use the following patterns which will be replaced:
%i by short snapshot ID
%I by long snapshot ID
%u by username
%h by hostname
%t by tags
%T by timestamp as specified by --time-template
The default path templates are:
"ids/%i"
"snapshots/%T"
"hosts/%h/%T"
"tags/%t/%T"
EXIT STATUS
===========
@@ -79,8 +61,7 @@ type MountOptions struct {
Hosts []string
Tags restic.TagLists
Paths []string
TimeTemplate string
PathTemplates []string
SnapshotTemplate string
}
var mountOptions MountOptions
@@ -97,21 +78,16 @@ func init() {
mountFlags.Var(&mountOptions.Tags, "tag", "only consider snapshots which include this `taglist`")
mountFlags.StringArrayVar(&mountOptions.Paths, "path", nil, "only consider snapshots which include this (absolute) `path`")
mountFlags.StringArrayVar(&mountOptions.PathTemplates, "path-template", nil, "set `template` for path names (can be specified multiple times)")
mountFlags.StringVar(&mountOptions.TimeTemplate, "snapshot-template", time.RFC3339, "set `template` to use for snapshot dirs")
mountFlags.StringVar(&mountOptions.TimeTemplate, "time-template", time.RFC3339, "set `template` to use for times")
_ = mountFlags.MarkDeprecated("snapshot-template", "use --time-template")
mountFlags.StringVar(&mountOptions.SnapshotTemplate, "snapshot-template", time.RFC3339, "set `template` to use for snapshot dirs")
}
func runMount(opts MountOptions, gopts GlobalOptions, args []string) error {
if opts.TimeTemplate == "" {
return errors.Fatal("time template string cannot be empty")
if opts.SnapshotTemplate == "" {
return errors.Fatal("snapshot template string cannot be empty")
}
if strings.HasPrefix(opts.TimeTemplate, "/") || strings.HasSuffix(opts.TimeTemplate, "/") {
return errors.Fatal("time template string cannot start or end with '/'")
if strings.ContainsAny(opts.SnapshotTemplate, `\/`) {
return errors.Fatal("snapshot template string contains a slash (/) or backslash (\\) character")
}
if len(args) == 0 {
return errors.Fatal("wrong number of parameters")
}
@@ -139,7 +115,7 @@ func runMount(opts MountOptions, gopts GlobalOptions, args []string) error {
mountpoint := args[0]
if _, err := resticfs.Stat(mountpoint); errors.Is(err, os.ErrNotExist) {
if _, err := resticfs.Stat(mountpoint); os.IsNotExist(errors.Cause(err)) {
Verbosef("Mountpoint %s doesn't exist\n", mountpoint)
return err
}
@@ -177,12 +153,11 @@ func runMount(opts MountOptions, gopts GlobalOptions, args []string) error {
}
cfg := fuse.Config{
OwnerIsRoot: opts.OwnerRoot,
Hosts: opts.Hosts,
Tags: opts.Tags,
Paths: opts.Paths,
TimeTemplate: opts.TimeTemplate,
PathTemplates: opts.PathTemplates,
OwnerIsRoot: opts.OwnerRoot,
Hosts: opts.Hosts,
Tags: opts.Tags,
Paths: opts.Paths,
SnapshotTemplate: opts.SnapshotTemplate,
}
root := fuse.NewRoot(repo, cfg)

View File

@@ -1,7 +1,6 @@
package main
import (
"context"
"math"
"sort"
"strconv"
@@ -9,7 +8,6 @@ import (
"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"
@@ -40,10 +38,7 @@ Exit status is 0 if the command was successful, and non-zero if there was any er
// PruneOptions collects all options for the cleanup command.
type PruneOptions struct {
DryRun bool
UnsafeNoSpaceRecovery string
unsafeRecovery bool
DryRun bool
MaxUnused string
maxUnusedBytes func(used uint64) (unused uint64) // calculates the number of unused bytes after repacking, according to MaxUnused
@@ -52,8 +47,6 @@ type PruneOptions struct {
MaxRepackBytes uint64
RepackCachableOnly bool
RepackSmall bool
RepackUncompressed bool
}
var pruneOptions PruneOptions
@@ -62,7 +55,6 @@ func init() {
cmdRoot.AddCommand(cmdPrune)
f := cmdPrune.Flags()
f.BoolVarP(&pruneOptions.DryRun, "dry-run", "n", false, "do not modify the repository, just print what would be done")
f.StringVarP(&pruneOptions.UnsafeNoSpaceRecovery, "unsafe-recover-no-free-space", "", "", "UNSAFE, READ THE DOCUMENTATION BEFORE USING! Try to recover a repository stuck with no free space. Do not use without trying out 'prune --max-repack-size 0' first.")
addPruneOptions(cmdPrune)
}
@@ -71,8 +63,6 @@ func addPruneOptions(c *cobra.Command) {
f.StringVar(&pruneOptions.MaxUnused, "max-unused", "5%", "tolerate given `limit` of unused data (absolute value in bytes with suffixes k/K, m/M, g/G, t/T, a value in % or the word 'unlimited')")
f.StringVar(&pruneOptions.MaxRepackSize, "max-repack-size", "", "maximum `size` to repack (allowed suffixes: k/K, m/M, g/G, t/T)")
f.BoolVar(&pruneOptions.RepackCachableOnly, "repack-cacheable-only", false, "only repack packs which are cacheable")
f.BoolVar(&pruneOptions.RepackSmall, "repack-small", false, "repack pack files below 80% of target pack size")
f.BoolVar(&pruneOptions.RepackUncompressed, "repack-uncompressed", false, "repack all uncompressed data")
}
func verifyPruneOptions(opts *PruneOptions) error {
@@ -84,10 +74,6 @@ func verifyPruneOptions(opts *PruneOptions) error {
}
opts.MaxRepackBytes = uint64(size)
}
if opts.UnsafeNoSpaceRecovery != "" {
// prevent repacking data to make sure users cannot get stuck.
opts.MaxRepackBytes = 0
}
maxUnused := strings.TrimSpace(opts.MaxUnused)
if maxUnused == "" {
@@ -140,31 +126,11 @@ func runPrune(opts PruneOptions, gopts GlobalOptions) error {
return err
}
if opts.RepackUncompressed && gopts.Compression == repository.CompressionOff {
return errors.Fatal("disabled compression and `--repack-uncompressed` are mutually exclusive")
}
repo, err := OpenRepository(gopts)
if err != nil {
return err
}
if repo.Backend().Connections() < 2 {
return errors.Fatal("prune requires a backend connection limit of at least two")
}
if repo.Config().Version < 2 && opts.RepackUncompressed {
return errors.Fatal("compression requires at least repository format version 2")
}
if opts.UnsafeNoSpaceRecovery != "" {
repoID := repo.Config().ID
if opts.UnsafeNoSpaceRecovery != repoID {
return errors.Fatalf("must pass id '%s' to --unsafe-recover-no-free-space", repoID)
}
opts.unsafeRecovery = true
}
lock, err := lockRepoExclusive(gopts.ctx, repo)
defer unlockRepo(lock)
if err != nil {
@@ -183,69 +149,26 @@ func runPruneWithRepo(opts PruneOptions, gopts GlobalOptions, repo *repository.R
}
Verbosef("loading indexes...\n")
// loading the index before the snapshots is ok, as we use an exclusive lock here
err := repo.LoadIndex(gopts.ctx)
if err != nil {
return err
}
plan, stats, err := planPrune(opts, gopts, repo, ignoreSnapshots)
usedBlobs, err := getUsedBlobs(gopts, repo, ignoreSnapshots)
if err != nil {
return err
}
err = printPruneStats(gopts, stats)
if err != nil {
return err
}
return doPrune(opts, gopts, repo, plan)
}
type pruneStats struct {
blobs struct {
used uint
duplicate uint
unused uint
remove uint
repack uint
repackrm uint
}
size struct {
used uint64
duplicate uint64
unused uint64
remove uint64
repack uint64
repackrm uint64
unref uint64
}
packs struct {
used uint
unused uint
partlyUsed uint
unref uint
keep uint
repack uint
remove uint
}
}
type prunePlan struct {
removePacksFirst restic.IDSet // packs to remove first (unreferenced packs)
repackPacks restic.IDSet // packs to repack
keepBlobs restic.BlobSet // blobs to keep during repacking
removePacks restic.IDSet // packs to remove
ignorePacks restic.IDSet // packs to ignore when rebuilding the index
return prune(opts, gopts, repo, usedBlobs)
}
type packInfo struct {
usedBlobs uint
unusedBlobs uint
usedSize uint64
unusedSize uint64
tpe restic.BlobType
uncompressed bool
usedBlobs uint
unusedBlobs uint
duplicateBlobs uint
usedSize uint64
unusedSize uint64
tpe restic.BlobType
}
type packInfoWithID struct {
@@ -253,53 +176,44 @@ type packInfoWithID struct {
packInfo
}
// planPrune selects which files to rewrite and which to delete and which blobs to keep.
// Also some summary statistics are returned.
func planPrune(opts PruneOptions, gopts GlobalOptions, repo restic.Repository, ignoreSnapshots restic.IDSet) (prunePlan, pruneStats, error) {
// prune selects which files to rewrite and then does that. The map usedBlobs is
// modified in the process.
func prune(opts PruneOptions, gopts GlobalOptions, repo restic.Repository, usedBlobs restic.BlobSet) error {
ctx := gopts.ctx
var stats pruneStats
usedBlobs, err := getUsedBlobs(gopts, repo, ignoreSnapshots)
if err != nil {
return prunePlan{}, stats, err
var stats struct {
blobs struct {
used uint
duplicate uint
unused uint
remove uint
repack uint
repackrm uint
}
size struct {
used uint64
duplicate uint64
unused uint64
remove uint64
repack uint64
repackrm uint64
unref uint64
}
packs struct {
used uint
unused uint
partlyUsed uint
keep uint
}
}
Verbosef("searching used packs...\n")
keepBlobs, indexPack, err := packInfoFromIndex(ctx, repo.Index(), usedBlobs, &stats)
if err != nil {
return prunePlan{}, stats, err
}
Verbosef("collecting packs for deletion and repacking\n")
plan, err := decidePackAction(ctx, opts, gopts, repo, indexPack, &stats)
if err != nil {
return prunePlan{}, stats, err
}
if len(plan.repackPacks) != 0 {
// when repacking, we do not want to keep blobs which are
// already contained in kept packs, so delete them from keepBlobs
for blob := range repo.Index().Each(ctx) {
if plan.removePacks.Has(blob.PackID) || plan.repackPacks.Has(blob.PackID) {
continue
}
keepBlobs.Delete(blob.BlobHandle)
}
} else {
// keepBlobs is only needed if packs are repacked
keepBlobs = nil
}
plan.keepBlobs = keepBlobs
return plan, stats, nil
}
func packInfoFromIndex(ctx context.Context, idx restic.MasterIndex, usedBlobs restic.BlobSet, stats *pruneStats) (restic.BlobSet, map[restic.ID]packInfo, error) {
keepBlobs := restic.NewBlobSet()
duplicateBlobs := make(map[restic.BlobHandle]uint8)
duplicateBlobs := restic.NewBlobSet()
// iterate over all blobs in index to find out which blobs are duplicates
for blob := range idx.Each(ctx) {
for blob := range repo.Index().Each(ctx) {
bh := blob.BlobHandle
size := uint64(blob.Length)
switch {
@@ -309,16 +223,7 @@ func packInfoFromIndex(ctx context.Context, idx restic.MasterIndex, usedBlobs re
stats.size.used += size
stats.blobs.used++
case keepBlobs.Has(bh): // duplicate blob
count, ok := duplicateBlobs[bh]
if !ok {
count = 2 // this one is already the second blob!
} else if count < math.MaxUint8 {
// don't overflow, but saturate count at 255
// this can lead to a non-optimal pack selection, but won't cause
// problems otherwise
count++
}
duplicateBlobs[bh] = count
duplicateBlobs.Insert(bh)
stats.size.duplicate += size
stats.blobs.duplicate++
default:
@@ -334,19 +239,19 @@ func packInfoFromIndex(ctx context.Context, idx restic.MasterIndex, usedBlobs re
"Will not start prune to prevent (additional) data loss!\n"+
"Please report this error (along with the output of the 'prune' run) at\n"+
"https://github.com/restic/restic/issues/new/choose\n", usedBlobs)
return nil, nil, errorIndexIncomplete
return errorIndexIncomplete
}
indexPack := make(map[restic.ID]packInfo)
// save computed pack header size
for pid, hdrSize := range pack.Size(ctx, idx, true) {
for pid, hdrSize := range repo.Index().PackSize(ctx, true) {
// initialize tpe with NumBlobTypes to indicate it's not set
indexPack[pid] = packInfo{tpe: restic.NumBlobTypes, usedSize: uint64(hdrSize)}
}
// iterate over all blobs in index to generate packInfo
for blob := range idx.Each(ctx) {
for blob := range repo.Index().Each(ctx) {
ip := indexPack[blob.PackID]
// Set blob type if not yet set
@@ -361,9 +266,10 @@ func packInfoFromIndex(ctx context.Context, idx restic.MasterIndex, usedBlobs re
bh := blob.BlobHandle
size := uint64(blob.Length)
_, isDuplicate := duplicateBlobs[bh]
switch {
case isDuplicate: // duplicate blobs will be handled later
case duplicateBlobs.Has(bh): // duplicate blob
ip.usedSize += size
ip.duplicateBlobs++
case keepBlobs.Has(bh): // used blob, not duplicate
ip.usedSize += size
ip.usedBlobs++
@@ -371,66 +277,23 @@ func packInfoFromIndex(ctx context.Context, idx restic.MasterIndex, usedBlobs re
ip.unusedSize += size
ip.unusedBlobs++
}
if !blob.IsCompressed() {
ip.uncompressed = true
}
// update indexPack
indexPack[blob.PackID] = ip
}
// if duplicate blobs exist, those will be set to either "used" or "unused":
// - mark only one occurence of duplicate blobs as used
// - if there are already some used blobs in a pack, possibly mark duplicates in this pack as "used"
// - if there are no used blobs in a pack, possibly mark duplicates as "unused"
if len(duplicateBlobs) > 0 {
// iterate again over all blobs in index (this is pretty cheap, all in-mem)
for blob := range idx.Each(ctx) {
bh := blob.BlobHandle
count, isDuplicate := duplicateBlobs[bh]
if !isDuplicate {
continue
}
ip := indexPack[blob.PackID]
size := uint64(blob.Length)
switch {
case count == 0:
// used duplicate exists -> mark as unused
ip.unusedSize += size
ip.unusedBlobs++
case ip.usedBlobs > 0, count == 1:
// other used blobs in pack or "last" occurency -> mark as used
ip.usedSize += size
ip.usedBlobs++
// let other occurences be marked as unused
duplicateBlobs[bh] = 0
default:
// mark as unused and decrease counter
ip.unusedSize += size
ip.unusedBlobs++
duplicateBlobs[bh] = count - 1
}
// update indexPack
indexPack[blob.PackID] = ip
}
}
return keepBlobs, indexPack, nil
}
func decidePackAction(ctx context.Context, opts PruneOptions, gopts GlobalOptions, repo restic.Repository, indexPack map[restic.ID]packInfo, stats *pruneStats) (prunePlan, error) {
Verbosef("collecting packs for deletion and repacking\n")
removePacksFirst := restic.NewIDSet()
removePacks := restic.NewIDSet()
repackPacks := restic.NewIDSet()
var repackCandidates []packInfoWithID
var repackSmallCandidates []packInfoWithID
repoVersion := repo.Config().Version
// only repack very small files by default
targetPackSize := repo.PackSize() / 25
if opts.RepackSmall {
// consider files with at least 80% of the target size as large enough
targetPackSize = repo.PackSize() / 5 * 4
repackAllPacksWithDuplicates := true
keep := func(p packInfo) {
stats.packs.keep++
if p.duplicateBlobs > 0 {
repackAllPacksWithDuplicates = false
}
}
// loop over all packs and decide what to do
@@ -445,7 +308,8 @@ func decidePackAction(ctx context.Context, opts PruneOptions, gopts GlobalOption
return nil
}
if p.unusedSize+p.usedSize != uint64(packSize) && p.usedBlobs != 0 {
if p.unusedSize+p.usedSize != uint64(packSize) &&
!(p.usedBlobs == 0 && p.duplicateBlobs == 0) {
// Pack size does not fit and pack is needed => error
// If the pack is not needed, this is no error, the pack can
// and will be simply removed, see below.
@@ -456,7 +320,7 @@ func decidePackAction(ctx context.Context, opts PruneOptions, gopts GlobalOption
// statistics
switch {
case p.usedBlobs == 0:
case p.usedBlobs == 0 && p.duplicateBlobs == 0:
stats.packs.unused++
case p.unusedBlobs == 0:
stats.packs.used++
@@ -464,18 +328,9 @@ func decidePackAction(ctx context.Context, opts PruneOptions, gopts GlobalOption
stats.packs.partlyUsed++
}
mustCompress := false
if repoVersion >= 2 {
// repo v2: always repack tree blobs if uncompressed
// compress data blobs if requested
mustCompress = (p.tpe == restic.TreeBlob || opts.RepackUncompressed) && p.uncompressed
}
// use a flag that pack must be compressed
p.uncompressed = mustCompress
// decide what to do
switch {
case p.usedBlobs == 0:
case p.usedBlobs == 0 && p.duplicateBlobs == 0:
// All blobs in pack are no longer used => remove pack!
removePacks.Insert(id)
stats.blobs.remove += p.unusedBlobs
@@ -483,15 +338,11 @@ func decidePackAction(ctx context.Context, opts PruneOptions, gopts GlobalOption
case opts.RepackCachableOnly && p.tpe == restic.DataBlob:
// if this is a data pack and --repack-cacheable-only is set => keep pack!
stats.packs.keep++
keep(p)
case p.unusedBlobs == 0 && p.tpe != restic.InvalidBlob && !mustCompress:
if packSize >= int64(targetPackSize) {
// All blobs in pack are used and not mixed => keep pack!
stats.packs.keep++
} else {
repackSmallCandidates = append(repackSmallCandidates, packInfoWithID{ID: id, packInfo: p})
}
case p.unusedBlobs == 0 && p.duplicateBlobs == 0 && p.tpe != restic.InvalidBlob:
// All blobs in pack are used and not duplicates/mixed => keep pack!
keep(p)
default:
// all other packs are candidates for repacking
@@ -504,7 +355,7 @@ func decidePackAction(ctx context.Context, opts PruneOptions, gopts GlobalOption
})
bar.Done()
if err != nil {
return prunePlan{}, err
return err
}
// At this point indexPacks contains only missing packs!
@@ -512,7 +363,7 @@ func decidePackAction(ctx context.Context, opts PruneOptions, gopts GlobalOption
// missing packs that are not needed can be ignored
ignorePacks := restic.NewIDSet()
for id, p := range indexPack {
if p.usedBlobs == 0 {
if p.usedBlobs == 0 && p.duplicateBlobs == 0 {
ignorePacks.Insert(id)
stats.blobs.remove += p.unusedBlobs
stats.size.remove += p.unusedSize
@@ -525,7 +376,7 @@ func decidePackAction(ctx context.Context, opts PruneOptions, gopts GlobalOption
for id := range indexPack {
Warnf(" %v\n", id)
}
return prunePlan{}, errorPacksMissing
return errorPacksMissing
}
if len(ignorePacks) != 0 {
Warnf("Missing but unneeded pack files are referenced in the index, will be repaired\n")
@@ -534,81 +385,65 @@ func decidePackAction(ctx context.Context, opts PruneOptions, gopts GlobalOption
}
}
if len(repackSmallCandidates) < 10 {
// too few small files to be worth the trouble, this also prevents endlessly repacking
// if there is just a single pack file below the target size
stats.packs.keep += uint(len(repackSmallCandidates))
} else {
repackCandidates = append(repackCandidates, repackSmallCandidates...)
}
// calculate limit for number of unused bytes in the repo after repacking
maxUnusedSizeAfter := opts.maxUnusedBytes(stats.size.used)
// Sort repackCandidates such that packs with highest ratio unused/used space are picked first.
// This is equivalent to sorting by unused / total space.
// Instead of unused[i] / used[i] > unused[j] / used[j] we use
// unused[i] * used[j] > unused[j] * used[i] as uint32*uint32 < uint64
// Moreover packs containing trees and too small packs are sorted to the beginning
// Morover duplicates and packs containing trees are sorted to the beginning
sort.Slice(repackCandidates, func(i, j int) bool {
pi := repackCandidates[i].packInfo
pj := repackCandidates[j].packInfo
switch {
case pi.duplicateBlobs > 0 && pj.duplicateBlobs == 0:
return true
case pj.duplicateBlobs > 0 && pi.duplicateBlobs == 0:
return false
case pi.tpe != restic.DataBlob && pj.tpe == restic.DataBlob:
return true
case pj.tpe != restic.DataBlob && pi.tpe == restic.DataBlob:
return false
case pi.unusedSize+pi.usedSize < uint64(targetPackSize) && pj.unusedSize+pj.usedSize >= uint64(targetPackSize):
return true
case pj.unusedSize+pj.usedSize < uint64(targetPackSize) && pi.unusedSize+pi.usedSize >= uint64(targetPackSize):
return false
}
return pi.unusedSize*pj.usedSize > pj.unusedSize*pi.usedSize
})
repack := func(id restic.ID, p packInfo) {
repackPacks.Insert(id)
stats.blobs.repack += p.unusedBlobs + p.usedBlobs
stats.blobs.repack += p.unusedBlobs + p.duplicateBlobs + p.usedBlobs
stats.size.repack += p.unusedSize + p.usedSize
stats.blobs.repackrm += p.unusedBlobs
stats.size.repackrm += p.unusedSize
}
// calculate limit for number of unused bytes in the repo after repacking
maxUnusedSizeAfter := opts.maxUnusedBytes(stats.size.used)
for _, p := range repackCandidates {
reachedUnusedSizeAfter := (stats.size.unused-stats.size.remove-stats.size.repackrm < maxUnusedSizeAfter)
reachedRepackSize := stats.size.repack+p.unusedSize+p.usedSize >= opts.MaxRepackBytes
packIsLargeEnough := p.unusedSize+p.usedSize >= uint64(targetPackSize)
switch {
case reachedRepackSize:
stats.packs.keep++
keep(p.packInfo)
case p.tpe != restic.DataBlob, p.uncompressed:
// repacking non-data packs / uncompressed-trees is only limited by repackSize
case p.duplicateBlobs > 0, p.tpe != restic.DataBlob:
// repacking duplicates/non-data is only limited by repackSize
repack(p.ID, p.packInfo)
case reachedUnusedSizeAfter && packIsLargeEnough:
case reachedUnusedSizeAfter:
// for all other packs stop repacking if tolerated unused size is reached.
stats.packs.keep++
keep(p.packInfo)
default:
repack(p.ID, p.packInfo)
}
}
stats.packs.unref = uint(len(removePacksFirst))
stats.packs.repack = uint(len(repackPacks))
stats.packs.remove = uint(len(removePacks))
// if all duplicates are repacked, print out correct statistics
if repackAllPacksWithDuplicates {
stats.blobs.repackrm += stats.blobs.duplicate
stats.size.repackrm += stats.size.duplicate
}
return prunePlan{removePacksFirst: removePacksFirst,
removePacks: removePacks,
repackPacks: repackPacks,
ignorePacks: ignorePacks,
}, nil
}
// printPruneStats prints out the statistics
func printPruneStats(gopts GlobalOptions, stats pruneStats) error {
Verboseff("\nused: %10d blobs / %s\n", stats.blobs.used, formatBytes(stats.size.used))
if stats.blobs.duplicate > 0 {
Verboseff("duplicates: %10d blobs / %s\n", stats.blobs.duplicate, formatBytes(stats.size.duplicate))
@@ -638,109 +473,73 @@ func printPruneStats(gopts GlobalOptions, stats pruneStats) error {
Verboseff("unused packs: %10d\n\n", stats.packs.unused)
Verboseff("to keep: %10d packs\n", stats.packs.keep)
Verboseff("to repack: %10d packs\n", stats.packs.repack)
Verboseff("to delete: %10d packs\n", stats.packs.remove)
if stats.packs.unref > 0 {
Verboseff("to delete: %10d unreferenced packs\n\n", stats.packs.unref)
Verboseff("to repack: %10d packs\n", len(repackPacks))
Verboseff("to delete: %10d packs\n", len(removePacks))
if len(removePacksFirst) > 0 {
Verboseff("to delete: %10d unreferenced packs\n\n", len(removePacksFirst))
}
return nil
}
// doPrune does the actual pruning:
// - remove unreferenced packs first
// - repack given pack files while keeping the given blobs
// - rebuild the index while ignoring all files that will be deleted
// - delete the files
// plan.removePacks and plan.ignorePacks are modified in this function.
func doPrune(opts PruneOptions, gopts GlobalOptions, repo restic.Repository, plan prunePlan) (err error) {
ctx := gopts.ctx
if opts.DryRun {
if !gopts.JSON && gopts.verbosity >= 2 {
if len(plan.removePacksFirst) > 0 {
Printf("Would have removed the following unreferenced packs:\n%v\n\n", plan.removePacksFirst)
if len(removePacksFirst) > 0 {
Printf("Would have removed the following unreferenced packs:\n%v\n\n", removePacksFirst)
}
Printf("Would have repacked and removed the following packs:\n%v\n\n", plan.repackPacks)
Printf("Would have removed the following no longer used packs:\n%v\n\n", plan.removePacks)
Printf("Would have repacked and removed the following packs:\n%v\n\n", repackPacks)
Printf("Would have removed the following no longer used packs:\n%v\n\n", removePacks)
}
// Always quit here if DryRun was set!
return nil
}
// unreferenced packs can be safely deleted first
if len(plan.removePacksFirst) != 0 {
if len(removePacksFirst) != 0 {
Verbosef("deleting unreferenced packs\n")
DeleteFiles(gopts, repo, plan.removePacksFirst, restic.PackFile)
DeleteFiles(gopts, repo, removePacksFirst, restic.PackFile)
}
if len(plan.repackPacks) != 0 {
if len(repackPacks) != 0 {
Verbosef("repacking packs\n")
bar := newProgressMax(!gopts.Quiet, uint64(len(plan.repackPacks)), "packs repacked")
_, err := repository.Repack(ctx, repo, repo, plan.repackPacks, plan.keepBlobs, bar)
bar := newProgressMax(!gopts.Quiet, uint64(len(repackPacks)), "packs repacked")
_, err := repository.Repack(ctx, repo, repackPacks, keepBlobs, bar)
bar.Done()
if err != nil {
return errors.Fatalf("%s", err)
}
// Also remove repacked packs
plan.removePacks.Merge(plan.repackPacks)
if len(plan.keepBlobs) != 0 {
Warnf("%v was not repacked\n\n"+
"Integrity check failed.\n"+
"Please report this error (along with the output of the 'prune' run) at\n"+
"https://github.com/restic/restic/issues/new/choose\n", plan.keepBlobs)
return errors.Fatal("internal error: blobs were not repacked")
}
removePacks.Merge(repackPacks)
}
if len(plan.ignorePacks) == 0 {
plan.ignorePacks = plan.removePacks
if len(ignorePacks) == 0 {
ignorePacks = removePacks
} else {
plan.ignorePacks.Merge(plan.removePacks)
ignorePacks.Merge(removePacks)
}
if opts.unsafeRecovery {
Verbosef("deleting index files\n")
indexFiles := repo.Index().(*repository.MasterIndex).IDs()
err = DeleteFilesChecked(gopts, repo, indexFiles, restic.IndexFile)
if err != nil {
return errors.Fatalf("%s", err)
}
} else if len(plan.ignorePacks) != 0 {
err = rebuildIndexFiles(gopts, repo, plan.ignorePacks, nil)
if len(ignorePacks) != 0 {
err = rebuildIndexFiles(gopts, repo, ignorePacks, nil)
if err != nil {
return errors.Fatalf("%s", err)
}
}
if len(plan.removePacks) != 0 {
Verbosef("removing %d old packs\n", len(plan.removePacks))
DeleteFiles(gopts, repo, plan.removePacks, restic.PackFile)
}
if opts.unsafeRecovery {
_, err = writeIndexFiles(gopts, repo, plan.ignorePacks, nil)
if err != nil {
return errors.Fatalf("%s", err)
}
if len(removePacks) != 0 {
Verbosef("removing %d old packs\n", len(removePacks))
DeleteFiles(gopts, repo, removePacks, restic.PackFile)
}
Verbosef("done\n")
return nil
}
func writeIndexFiles(gopts GlobalOptions, repo restic.Repository, removePacks restic.IDSet, extraObsolete restic.IDs) (restic.IDSet, error) {
func rebuildIndexFiles(gopts GlobalOptions, repo restic.Repository, removePacks restic.IDSet, extraObsolete restic.IDs) error {
Verbosef("rebuilding index\n")
bar := newProgressMax(!gopts.Quiet, 0, "packs processed")
obsoleteIndexes, err := repo.Index().Save(gopts.ctx, repo, removePacks, extraObsolete, bar)
idx := (repo.Index()).(*repository.MasterIndex)
packcount := uint64(len(idx.Packs(removePacks)))
bar := newProgressMax(!gopts.Quiet, packcount, "packs processed")
obsoleteIndexes, err := idx.Save(gopts.ctx, repo, removePacks, extraObsolete, bar)
bar.Done()
return obsoleteIndexes, err
}
func rebuildIndexFiles(gopts GlobalOptions, repo restic.Repository, removePacks restic.IDSet, extraObsolete restic.IDs) error {
obsoleteIndexes, err := writeIndexFiles(gopts, repo, removePacks, extraObsolete)
if err != nil {
return err
}
@@ -754,18 +553,17 @@ func getUsedBlobs(gopts GlobalOptions, repo restic.Repository, ignoreSnapshots r
var snapshotTrees restic.IDs
Verbosef("loading all snapshots...\n")
err = restic.ForAllSnapshots(gopts.ctx, repo.Backend(), repo, ignoreSnapshots,
err = restic.ForAllSnapshots(gopts.ctx, repo, ignoreSnapshots,
func(id restic.ID, sn *restic.Snapshot, err error) error {
debug.Log("add snapshot %v (tree %v, error %v)", id, *sn.Tree, err)
if err != nil {
debug.Log("failed to load snapshot %v (error %v)", id, err)
return err
}
debug.Log("add snapshot %v (tree %v)", id, *sn.Tree)
snapshotTrees = append(snapshotTrees, *sn.Tree)
return nil
})
if err != nil {
return nil, errors.Fatalf("failed loading snapshot: %v", err)
return nil, err
}
Verbosef("finding data that is still in use for %d snapshots\n", len(snapshotTrees))
@@ -778,7 +576,7 @@ func getUsedBlobs(gopts GlobalOptions, repo restic.Repository, ignoreSnapshots r
err = restic.FindUsedBlobs(ctx, repo, snapshotTrees, usedBlobs, bar)
if err != nil {
if repo.Backend().IsNotExist(err) {
return nil, errors.Fatal("unable to load a tree from the repository: " + err.Error())
return nil, errors.Fatal("unable to load a tree from the repo: " + err.Error())
}
return nil, err

View File

@@ -1,7 +1,6 @@
package main
import (
"github.com/restic/restic/internal/pack"
"github.com/restic/restic/internal/repository"
"github.com/restic/restic/internal/restic"
@@ -98,7 +97,7 @@ func rebuildIndex(opts RebuildIndexOptions, gopts GlobalOptions, repo *repositor
if err != nil {
return err
}
packSizeFromIndex = pack.Size(ctx, repo.Index(), false)
packSizeFromIndex = repo.Index().PackSize(ctx, false)
}
Verbosef("getting pack files to read...\n")

View File

@@ -5,11 +5,9 @@ import (
"os"
"time"
"github.com/restic/restic/internal/backend"
"github.com/restic/restic/internal/errors"
"github.com/restic/restic/internal/restic"
"github.com/spf13/cobra"
"golang.org/x/sync/errgroup"
)
var cmdRecover = &cobra.Command{
@@ -52,11 +50,6 @@ func runRecover(gopts GlobalOptions) error {
return err
}
snapshotLister, err := backend.MemorizeList(gopts.ctx, repo.Backend(), restic.SnapshotFile)
if err != nil {
return err
}
Verbosef("load index files\n")
if err = repo.LoadIndex(gopts.ctx); err != nil {
return err
@@ -75,7 +68,7 @@ func runRecover(gopts GlobalOptions) error {
Verbosef("load %d trees\n", len(trees))
bar := newProgressMax(!gopts.Quiet, uint64(len(trees)), "trees loaded")
for id := range trees {
tree, err := restic.LoadTree(gopts.ctx, repo, id)
tree, err := repo.LoadTree(gopts.ctx, id)
if err != nil {
Warnf("unable to load tree %v: %v\n", id.Str(), err)
continue
@@ -91,7 +84,7 @@ func runRecover(gopts GlobalOptions) error {
bar.Done()
Verbosef("load snapshots\n")
err = restic.ForAllSnapshots(gopts.ctx, snapshotLister, repo, nil, func(id restic.ID, sn *restic.Snapshot, err error) error {
err = restic.ForAllSnapshots(gopts.ctx, repo, nil, func(id restic.ID, sn *restic.Snapshot, err error) error {
trees[*sn.Tree] = true
return nil
})
@@ -132,26 +125,14 @@ func runRecover(gopts GlobalOptions) error {
}
}
wg, ctx := errgroup.WithContext(gopts.ctx)
repo.StartPackUploader(ctx, wg)
var treeID restic.ID
wg.Go(func() error {
var err error
treeID, err = restic.SaveTree(ctx, repo, tree)
if err != nil {
return errors.Fatalf("unable to save new tree to the repository: %v", err)
}
err = repo.Flush(ctx)
if err != nil {
return errors.Fatalf("unable to save blobs to the repository: %v", err)
}
return nil
})
err = wg.Wait()
treeID, err := repo.SaveTree(gopts.ctx, tree)
if err != nil {
return err
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)
}
return createSnapshot(gopts.ctx, "/recover", hostname, []string{"recovered"}, repo, &treeID)
@@ -166,7 +147,7 @@ func createSnapshot(ctx context.Context, name, hostname string, tags []string, r
sn.Tree = tree
id, err := restic.SaveSnapshot(ctx, repo, sn)
id, err := repo.SaveJSONUnpacked(ctx, restic.SnapshotFile, sn)
if err != nil {
return errors.Fatalf("unable to save snapshot: %v", err)
}

View File

@@ -70,28 +70,6 @@ func runRestore(opts RestoreOptions, gopts GlobalOptions, args []string) error {
hasExcludes := len(opts.Exclude) > 0 || len(opts.InsensitiveExclude) > 0
hasIncludes := len(opts.Include) > 0 || len(opts.InsensitiveInclude) > 0
// Validate provided patterns
if len(opts.Exclude) > 0 {
if valid, invalidPatterns := filter.ValidatePatterns(opts.Exclude); !valid {
return errors.Fatalf("--exclude: invalid pattern(s) provided:\n%s", strings.Join(invalidPatterns, "\n"))
}
}
if len(opts.InsensitiveExclude) > 0 {
if valid, invalidPatterns := filter.ValidatePatterns(opts.InsensitiveExclude); !valid {
return errors.Fatalf("--iexclude: invalid pattern(s) provided:\n%s", strings.Join(invalidPatterns, "\n"))
}
}
if len(opts.Include) > 0 {
if valid, invalidPatterns := filter.ValidatePatterns(opts.Include); !valid {
return errors.Fatalf("--include: invalid pattern(s) provided:\n%s", strings.Join(invalidPatterns, "\n"))
}
}
if len(opts.InsensitiveInclude) > 0 {
if valid, invalidPatterns := filter.ValidatePatterns(opts.InsensitiveInclude); !valid {
return errors.Fatalf("--iinclude: invalid pattern(s) provided:\n%s", strings.Join(invalidPatterns, "\n"))
}
}
for i, str := range opts.InsensitiveExclude {
opts.InsensitiveExclude[i] = strings.ToLower(str)
}
@@ -132,25 +110,25 @@ func runRestore(opts RestoreOptions, gopts GlobalOptions, args []string) error {
}
}
err = repo.LoadIndex(ctx)
if err != nil {
return err
}
var id restic.ID
if snapshotIDString == "latest" {
id, err = restic.FindLatestSnapshot(ctx, repo.Backend(), repo, opts.Paths, opts.Tags, opts.Hosts, nil)
id, err = restic.FindLatestSnapshot(ctx, repo, opts.Paths, opts.Tags, opts.Hosts, nil)
if err != nil {
Exitf(1, "latest snapshot for criteria not found: %v Paths:%v Hosts:%v", err, opts.Paths, opts.Hosts)
}
} else {
id, err = restic.FindSnapshot(ctx, repo.Backend(), snapshotIDString)
id, err = restic.FindSnapshot(ctx, repo, snapshotIDString)
if err != nil {
Exitf(1, "invalid id %q: %v", snapshotIDString, err)
}
}
err = repo.LoadIndex(ctx)
if err != nil {
return err
}
res, err := restorer.NewRestorer(ctx, repo, id)
if err != nil {
Exitf(2, "creating restorer failed: %v\n", err)

View File

@@ -58,7 +58,7 @@ func init() {
panic(err)
}
f.IntVar(&snapshotOptions.Latest, "latest", 0, "only show the last `n` snapshots for each host and path")
f.StringVarP(&snapshotOptions.GroupBy, "group-by", "g", "", "`group` snapshots by host, paths and/or tags, separated by comma")
f.StringVarP(&snapshotOptions.GroupBy, "group-by", "g", "", "string for grouping snapshots by host,paths,tags")
}
func runSnapshots(opts SnapshotOptions, gopts GlobalOptions, args []string) error {
@@ -79,7 +79,7 @@ func runSnapshots(opts SnapshotOptions, gopts GlobalOptions, args []string) erro
defer cancel()
var snapshots restic.Snapshots
for sn := range FindFilteredSnapshots(ctx, repo.Backend(), repo, opts.Hosts, opts.Tags, opts.Paths, args) {
for sn := range FindFilteredSnapshots(ctx, repo, opts.Hosts, opts.Tags, opts.Paths, args) {
snapshots = append(snapshots, sn)
}
snapshotGroups, grouped, err := restic.GroupSnapshots(snapshots, opts.GroupBy)

View File

@@ -6,7 +6,6 @@ import (
"fmt"
"path/filepath"
"github.com/restic/restic/internal/backend"
"github.com/restic/restic/internal/restic"
"github.com/restic/restic/internal/walker"
@@ -87,6 +86,10 @@ func runStats(gopts GlobalOptions, args []string) error {
return err
}
if err = repo.LoadIndex(ctx); err != nil {
return err
}
if !gopts.NoLock {
lock, err := lockRepo(ctx, repo)
defer unlockRepo(lock)
@@ -95,15 +98,6 @@ func runStats(gopts GlobalOptions, args []string) error {
}
}
snapshotLister, err := backend.MemorizeList(gopts.ctx, repo.Backend(), restic.SnapshotFile)
if err != nil {
return err
}
if err = repo.LoadIndex(ctx); err != nil {
return err
}
if !gopts.JSON {
Printf("scanning...\n")
}
@@ -111,12 +105,13 @@ func runStats(gopts GlobalOptions, args []string) error {
// create a container for the stats (and other needed state)
stats := &statsContainer{
uniqueFiles: make(map[fileID]struct{}),
uniqueInodes: make(map[uint64]struct{}),
fileBlobs: make(map[string]restic.IDSet),
blobs: restic.NewBlobSet(),
SnapshotsCount: 0,
snapshotsCount: 0,
}
for sn := range FindFilteredSnapshots(ctx, snapshotLister, repo, statsOptions.Hosts, statsOptions.Tags, statsOptions.Paths, args) {
for sn := range FindFilteredSnapshots(ctx, repo, statsOptions.Hosts, statsOptions.Tags, statsOptions.Paths, args) {
err = statsWalkSnapshot(ctx, sn, repo, stats)
if err != nil {
return fmt.Errorf("error walking snapshot: %v", err)
@@ -130,11 +125,11 @@ func runStats(gopts GlobalOptions, args []string) error {
if statsOptions.countMode == countModeRawData {
// the blob handles have been collected, but not yet counted
for blobHandle := range stats.blobs {
pbs := repo.Index().Lookup(blobHandle)
if len(pbs) == 0 {
blobSize, found := repo.LookupBlobSize(blobHandle.ID, blobHandle.Type)
if !found {
return fmt.Errorf("blob %v not found", blobHandle)
}
stats.TotalSize += uint64(pbs[0].Length)
stats.TotalSize += uint64(blobSize)
stats.TotalBlobCount++
}
}
@@ -148,7 +143,7 @@ func runStats(gopts GlobalOptions, args []string) error {
}
Printf("Stats in %s mode:\n", statsOptions.countMode)
Printf("Snapshots processed: %d\n", stats.SnapshotsCount)
Printf("Snapshots processed: %d\n", stats.snapshotsCount)
if stats.TotalBlobCount > 0 {
Printf(" Total Blob Count: %d\n", stats.TotalBlobCount)
@@ -166,7 +161,7 @@ func statsWalkSnapshot(ctx context.Context, snapshot *restic.Snapshot, repo rest
return fmt.Errorf("snapshot %s has nil tree", snapshot.ID().Str())
}
stats.SnapshotsCount++
stats.snapshotsCount++
if statsOptions.countMode == countModeRawData {
// count just the sizes of unique blobs; we don't need to walk the tree
@@ -174,8 +169,7 @@ func statsWalkSnapshot(ctx context.Context, snapshot *restic.Snapshot, repo rest
return restic.FindUsedBlobs(ctx, repo, restic.IDs{*snapshot.Tree}, stats.blobs, nil)
}
uniqueInodes := make(map[uint64]struct{})
err := walker.Walk(ctx, repo, *snapshot.Tree, restic.NewIDSet(), statsWalkTree(repo, stats, uniqueInodes))
err := walker.Walk(ctx, repo, *snapshot.Tree, restic.NewIDSet(), statsWalkTree(repo, stats))
if err != nil {
return fmt.Errorf("walking tree %s: %v", *snapshot.Tree, err)
}
@@ -183,7 +177,7 @@ func statsWalkSnapshot(ctx context.Context, snapshot *restic.Snapshot, repo rest
return nil
}
func statsWalkTree(repo restic.Repository, stats *statsContainer, uniqueInodes map[uint64]struct{}) walker.WalkFunc {
func statsWalkTree(repo restic.Repository, stats *statsContainer) walker.WalkFunc {
return func(parentTreeID restic.ID, npath string, node *restic.Node, nodeErr error) (bool, error) {
if nodeErr != nil {
return true, nodeErr
@@ -242,8 +236,8 @@ func statsWalkTree(repo restic.Repository, stats *statsContainer, uniqueInodes m
// if inodes are present, only count each inode once
// (hard links do not increase restore size)
if _, ok := uniqueInodes[node.Inode]; !ok || node.Inode == 0 {
uniqueInodes[node.Inode] = struct{}{}
if _, ok := stats.uniqueInodes[node.Inode]; !ok || node.Inode == 0 {
stats.uniqueInodes[node.Inode] = struct{}{}
stats.TotalSize += node.Size
}
@@ -285,13 +279,15 @@ type statsContainer struct {
TotalSize uint64 `json:"total_size"`
TotalFileCount uint64 `json:"total_file_count"`
TotalBlobCount uint64 `json:"total_blob_count,omitempty"`
// holds count of all considered snapshots
SnapshotsCount int `json:"snapshots_count"`
// uniqueFiles marks visited files according to their
// contents (hashed sequence of content blob IDs)
uniqueFiles map[fileID]struct{}
// uniqueInodes marks visited files according to their
// inode # (hashed sequence of inode numbers)
uniqueInodes map[uint64]struct{}
// fileBlobs maps a file name (path) to the set of
// blobs that have been seen as a part of the file
fileBlobs map[string]restic.IDSet
@@ -299,6 +295,9 @@ type statsContainer struct {
// blobs is used to count individual unique blobs,
// independent of references to files
blobs restic.BlobSet
// holds count of all considered snapshots
snapshotsCount int
}
// fileID is a 256-bit hash that distinguishes unique files.

View File

@@ -82,13 +82,17 @@ func changeTags(ctx context.Context, repo *repository.Repository, sn *restic.Sna
}
// Save the new snapshot.
id, err := restic.SaveSnapshot(ctx, repo, sn)
id, err := repo.SaveJSONUnpacked(ctx, restic.SnapshotFile, sn)
if err != nil {
return false, err
}
debug.Log("new snapshot saved as %v", id)
if err = repo.Flush(ctx); err != nil {
return false, err
}
// Remove the old snapshot.
h := restic.Handle{Type: restic.SnapshotFile, Name: sn.ID().String()}
if err = repo.Backend().Remove(ctx, h); err != nil {
@@ -125,7 +129,7 @@ func runTag(opts TagOptions, gopts GlobalOptions, args []string) error {
changeCnt := 0
ctx, cancel := context.WithCancel(gopts.ctx)
defer cancel()
for sn := range FindFilteredSnapshots(ctx, repo.Backend(), repo, opts.Hosts, opts.Tags, opts.Paths, args) {
for sn := range FindFilteredSnapshots(ctx, repo, opts.Hosts, opts.Tags, opts.Paths, args) {
changed, err := changeTags(ctx, repo, sn, opts.SetTags.Flatten(), opts.AddTags.Flatten(), opts.RemoveTags.Flatten())
if err != nil {
Warnf("unable to modify the tags for snapshot ID %q, ignoring: %v\n", sn.ID(), err)

View File

@@ -18,6 +18,8 @@ func DeleteFilesChecked(gopts GlobalOptions, repo restic.Repository, fileList re
return deleteFiles(gopts, false, repo, fileList, fileType)
}
const numDeleteWorkers = 8
// deleteFiles deletes the given fileList of fileType in parallel
// if ignoreError=true, it will print a warning if there was an error, else it will abort.
func deleteFiles(gopts GlobalOptions, ignoreError bool, repo restic.Repository, fileList restic.IDSet, fileType restic.FileType) error {
@@ -38,9 +40,7 @@ func deleteFiles(gopts GlobalOptions, ignoreError bool, repo restic.Repository,
bar := newProgressMax(!gopts.JSON && !gopts.Quiet, uint64(totalCount), "files deleted")
defer bar.Done()
// deleting files is IO-bound
workerCount := repo.Connections()
for i := 0; i < int(workerCount); i++ {
for i := 0; i < numDeleteWorkers; i++ {
wg.Go(func() error {
for id := range fileChan {
h := restic.Handle{Type: fileType, Name: id.String()}

View File

@@ -3,39 +3,33 @@ package main
import (
"context"
"github.com/restic/restic/internal/backend"
"github.com/restic/restic/internal/repository"
"github.com/restic/restic/internal/restic"
)
// FindFilteredSnapshots yields Snapshots, either given explicitly by `snapshotIDs` or filtered from the list of all snapshots.
func FindFilteredSnapshots(ctx context.Context, be restic.Lister, loader restic.LoaderUnpacked, hosts []string, tags []restic.TagList, paths []string, snapshotIDs []string) <-chan *restic.Snapshot {
func FindFilteredSnapshots(ctx context.Context, repo *repository.Repository, hosts []string, tags []restic.TagList, paths []string, snapshotIDs []string) <-chan *restic.Snapshot {
out := make(chan *restic.Snapshot)
go func() {
defer close(out)
if len(snapshotIDs) != 0 {
// memorize snapshots list to prevent repeated backend listings
be, err := backend.MemorizeList(ctx, be, restic.SnapshotFile)
if err != nil {
Warnf("could not load snapshots: %v\n", err)
return
}
var (
id restic.ID
usedFilter bool
err error
)
ids := make(restic.IDs, 0, len(snapshotIDs))
// Process all snapshot IDs given as arguments.
for _, s := range snapshotIDs {
if s == "latest" {
usedFilter = true
id, err = restic.FindLatestSnapshot(ctx, be, loader, paths, tags, hosts, nil)
id, err = restic.FindLatestSnapshot(ctx, repo, paths, tags, hosts, nil)
if err != nil {
Warnf("Ignoring %q, no snapshot matched given filter (Paths:%v Tags:%v Hosts:%v)\n", s, paths, tags, hosts)
continue
}
} else {
id, err = restic.FindSnapshot(ctx, be, s)
id, err = restic.FindSnapshot(ctx, repo, s)
if err != nil {
Warnf("Ignoring %q: %v\n", s, err)
continue
@@ -50,7 +44,7 @@ func FindFilteredSnapshots(ctx context.Context, be restic.Lister, loader restic.
}
for _, id := range ids.Uniq() {
sn, err := restic.LoadSnapshot(ctx, loader, id)
sn, err := restic.LoadSnapshot(ctx, repo, id)
if err != nil {
Warnf("Ignoring %q, could not load snapshot: %v\n", id, err)
continue
@@ -64,7 +58,7 @@ func FindFilteredSnapshots(ctx context.Context, be restic.Lister, loader restic.
return
}
snapshots, err := restic.FindFilteredSnapshots(ctx, be, loader, hosts, tags, paths)
snapshots, err := restic.FindFilteredSnapshots(ctx, repo, hosts, tags, paths)
if err != nil {
Warnf("could not load snapshots: %v\n", err)
return

View File

@@ -8,7 +8,6 @@ import (
"os"
"path/filepath"
"runtime"
"strconv"
"strings"
"syscall"
"time"
@@ -17,7 +16,6 @@ import (
"github.com/restic/restic/internal/backend/azure"
"github.com/restic/restic/internal/backend/b2"
"github.com/restic/restic/internal/backend/gs"
"github.com/restic/restic/internal/backend/limiter"
"github.com/restic/restic/internal/backend/local"
"github.com/restic/restic/internal/backend/location"
"github.com/restic/restic/internal/backend/rclone"
@@ -28,6 +26,7 @@ import (
"github.com/restic/restic/internal/cache"
"github.com/restic/restic/internal/debug"
"github.com/restic/restic/internal/fs"
"github.com/restic/restic/internal/limiter"
"github.com/restic/restic/internal/options"
"github.com/restic/restic/internal/repository"
"github.com/restic/restic/internal/restic"
@@ -38,10 +37,10 @@ import (
"os/exec"
"golang.org/x/term"
"golang.org/x/crypto/ssh/terminal"
)
var version = "0.14.0"
var version = "0.13.1"
// TimeFormat is the format used for all timestamps printed by restic.
const TimeFormat = "2006-01-02 15:04:05"
@@ -61,12 +60,13 @@ type GlobalOptions struct {
JSON bool
CacheDir string
NoCache bool
CACerts []string
InsecureTLS bool
TLSClientCert string
CleanupCache bool
Compression repository.CompressionMode
PackSize uint
backend.TransportOptions
limiter.Limits
LimitUploadKb int
LimitDownloadKb int
ctx context.Context
password string
@@ -104,9 +104,6 @@ func init() {
return nil
})
// parse target pack size from env, on error the default value will be used
targetPackSize, _ := strconv.ParseUint(os.Getenv("RESTIC_PACK_SIZE"), 10, 32)
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.RepositoryFile, "repository-file", "", os.Getenv("RESTIC_REPOSITORY_FILE"), "`file` to read the repository location from (default: $RESTIC_REPOSITORY_FILE)")
@@ -119,24 +116,16 @@ func init() {
f.BoolVarP(&globalOptions.JSON, "json", "", false, "set output mode to JSON for commands that support it")
f.StringVar(&globalOptions.CacheDir, "cache-dir", "", "set the cache `directory`. (default: use system default cache directory)")
f.BoolVar(&globalOptions.NoCache, "no-cache", false, "do not use a local cache")
f.StringSliceVar(&globalOptions.RootCertFilenames, "cacert", nil, "`file` to load root certificates from (default: use system certificates)")
f.StringVar(&globalOptions.TLSClientCertKeyFilename, "tls-client-cert", "", "path to a `file` containing PEM encoded TLS client certificate and private key")
f.BoolVar(&globalOptions.InsecureTLS, "insecure-tls", false, "skip TLS certificate verification when connecting to the repository (insecure)")
f.StringSliceVar(&globalOptions.CACerts, "cacert", nil, "`file` to load root certificates from (default: use system certificates)")
f.StringVar(&globalOptions.TLSClientCert, "tls-client-cert", "", "path to a `file` containing PEM encoded TLS client certificate and private key")
f.BoolVar(&globalOptions.InsecureTLS, "insecure-tls", false, "skip TLS certificate verification when connecting to the repo (insecure)")
f.BoolVar(&globalOptions.CleanupCache, "cleanup-cache", false, "auto remove old cache directories")
f.Var(&globalOptions.Compression, "compression", "compression mode (only available for repository format version 2), one of (auto|off|max)")
f.IntVar(&globalOptions.Limits.UploadKb, "limit-upload", 0, "limits uploads to a maximum rate in KiB/s. (default: unlimited)")
f.IntVar(&globalOptions.Limits.DownloadKb, "limit-download", 0, "limits downloads to a maximum rate in KiB/s. (default: unlimited)")
f.UintVar(&globalOptions.PackSize, "pack-size", uint(targetPackSize), "set target pack size in MiB, created pack files may be larger (default: $RESTIC_PACK_SIZE)")
f.IntVar(&globalOptions.LimitUploadKb, "limit-upload", 0, "limits uploads to a maximum rate in KiB/s. (default: unlimited)")
f.IntVar(&globalOptions.LimitDownloadKb, "limit-download", 0, "limits downloads to a maximum rate in KiB/s. (default: unlimited)")
f.StringSliceVarP(&globalOptions.Options, "option", "o", []string{}, "set extended option (`key=value`, can be specified multiple times)")
// Use our "generate" command instead of the cobra provided "completion" command
cmdRoot.CompletionOptions.DisableDefaultCmd = true
comp := os.Getenv("RESTIC_COMPRESSION")
if comp != "" {
// ignore error as there's no good way to handle it
_ = globalOptions.Compression.Set(comp)
}
restoreTerminal()
}
@@ -156,13 +145,13 @@ func checkErrno(err error) error {
}
func stdinIsTerminal() bool {
return term.IsTerminal(int(os.Stdin.Fd()))
return terminal.IsTerminal(int(os.Stdin.Fd()))
}
func stdoutIsTerminal() bool {
// mintty on windows can use pipes which behave like a posix terminal,
// but which are not a terminal handle
return term.IsTerminal(int(os.Stdout.Fd())) || stdoutCanUpdateStatus()
return terminal.IsTerminal(int(os.Stdout.Fd())) || stdoutCanUpdateStatus()
}
func stdoutCanUpdateStatus() bool {
@@ -170,7 +159,7 @@ func stdoutCanUpdateStatus() bool {
}
func stdoutTerminalWidth() int {
w, _, err := term.GetSize(int(os.Stdout.Fd()))
w, _, err := terminal.GetSize(int(os.Stdout.Fd()))
if err != nil {
return 0
}
@@ -183,12 +172,12 @@ func stdoutTerminalWidth() int {
// program execution must revert changes to the terminal configuration itself.
// The terminal configuration is only restored while reading a password.
func restoreTerminal() {
if !term.IsTerminal(int(os.Stdout.Fd())) {
if !terminal.IsTerminal(int(os.Stdout.Fd())) {
return
}
fd := int(os.Stdout.Fd())
state, err := term.GetState(fd)
state, err := terminal.GetState(fd)
if err != nil {
fmt.Fprintf(os.Stderr, "unable to get terminal state: %v\n", err)
return
@@ -203,7 +192,7 @@ func restoreTerminal() {
if !isReadingPassword {
return nil
}
err := checkErrno(term.Restore(fd, state))
err := checkErrno(terminal.Restore(fd, state))
if err != nil {
fmt.Fprintf(os.Stderr, "unable to restore terminal state: %v\n", err)
}
@@ -306,7 +295,7 @@ func resolvePassword(opts GlobalOptions, envStr string) (string, error) {
}
if opts.PasswordFile != "" {
s, err := textfile.Read(opts.PasswordFile)
if errors.Is(err, os.ErrNotExist) {
if os.IsNotExist(errors.Cause(err)) {
return "", errors.Fatalf("%s does not exist", opts.PasswordFile)
}
return strings.TrimSpace(string(s)), errors.Wrap(err, "Readfile")
@@ -333,7 +322,7 @@ func readPassword(in io.Reader) (password string, err error) {
func readPasswordTerminal(in *os.File, out io.Writer, prompt string) (password string, err error) {
fmt.Fprint(out, prompt)
isReadingPassword = true
buf, err := term.ReadPassword(int(in.Fd()))
buf, err := terminal.ReadPassword(int(in.Fd()))
isReadingPassword = false
fmt.Fprintln(out)
if err != nil {
@@ -407,7 +396,7 @@ func ReadRepo(opts GlobalOptions) (string, error) {
}
s, err := textfile.Read(opts.RepositoryFile)
if errors.Is(err, os.ErrNotExist) {
if os.IsNotExist(errors.Cause(err)) {
return "", errors.Fatalf("%s does not exist", opts.RepositoryFile)
}
if err != nil {
@@ -446,13 +435,7 @@ func OpenRepository(opts GlobalOptions) (*repository.Repository, error) {
}
}
s, err := repository.New(be, repository.Options{
Compression: opts.Compression,
PackSize: opts.PackSize * 1024 * 1024,
})
if err != nil {
return nil, err
}
s := repository.New(be)
passwordTriesLeft := 1
if stdinIsTerminal() && opts.password == "" {
@@ -472,7 +455,7 @@ func OpenRepository(opts GlobalOptions) (*repository.Repository, error) {
err = s.SearchKey(opts.ctx, opts.password, maxKeys, opts.KeyHint)
if err != nil && passwordTriesLeft > 1 {
opts.password = ""
fmt.Fprintf(os.Stderr, "%s. Try again\n", err)
fmt.Printf("%s. Try again\n", err)
}
}
if err != nil {
@@ -488,7 +471,7 @@ func OpenRepository(opts GlobalOptions) (*repository.Repository, error) {
id = id[:8]
}
if !opts.JSON {
Verbosef("repository %v opened (repository version %v) successfully, password is correct\n", id, s.Config().Version)
Verbosef("repository %v opened successfully, password is correct\n", id)
}
}
@@ -570,13 +553,13 @@ func parseConfig(loc location.Location, opts options.Options) (interface{}, erro
cfg.KeyID = os.Getenv("AWS_ACCESS_KEY_ID")
}
if cfg.Secret.String() == "" {
cfg.Secret = options.NewSecretString(os.Getenv("AWS_SECRET_ACCESS_KEY"))
if cfg.Secret == "" {
cfg.Secret = os.Getenv("AWS_SECRET_ACCESS_KEY")
}
if cfg.KeyID == "" && cfg.Secret.String() != "" {
if cfg.KeyID == "" && cfg.Secret != "" {
return nil, errors.Fatalf("unable to open S3 backend: Key ID ($AWS_ACCESS_KEY_ID) is empty")
} else if cfg.KeyID != "" && cfg.Secret.String() == "" {
} else if cfg.KeyID != "" && cfg.Secret == "" {
return nil, errors.Fatalf("unable to open S3 backend: Secret ($AWS_SECRET_ACCESS_KEY) is empty")
}
@@ -610,12 +593,8 @@ func parseConfig(loc location.Location, opts options.Options) (interface{}, erro
cfg.AccountName = os.Getenv("AZURE_ACCOUNT_NAME")
}
if cfg.AccountKey.String() == "" {
cfg.AccountKey = options.NewSecretString(os.Getenv("AZURE_ACCOUNT_KEY"))
}
if cfg.AccountSAS.String() == "" {
cfg.AccountSAS = options.NewSecretString(os.Getenv("AZURE_ACCOUNT_SAS"))
if cfg.AccountKey == "" {
cfg.AccountKey = os.Getenv("AZURE_ACCOUNT_KEY")
}
if err := opts.Apply(loc.Scheme, &cfg); err != nil {
@@ -650,11 +629,11 @@ func parseConfig(loc location.Location, opts options.Options) (interface{}, erro
return nil, errors.Fatalf("unable to open B2 backend: Account ID ($B2_ACCOUNT_ID) is empty")
}
if cfg.Key.String() == "" {
cfg.Key = options.NewSecretString(os.Getenv("B2_ACCOUNT_KEY"))
if cfg.Key == "" {
cfg.Key = os.Getenv("B2_ACCOUNT_KEY")
}
if cfg.Key.String() == "" {
if cfg.Key == "" {
return nil, errors.Fatalf("unable to open B2 backend: Key ($B2_ACCOUNT_KEY) is empty")
}
@@ -700,13 +679,18 @@ func open(s string, gopts GlobalOptions, opts options.Options) (restic.Backend,
return nil, err
}
rt, err := backend.Transport(globalOptions.TransportOptions)
tropts := backend.TransportOptions{
RootCertFilenames: globalOptions.CACerts,
TLSClientCertKeyFilename: globalOptions.TLSClientCert,
InsecureTLS: globalOptions.InsecureTLS,
}
rt, err := backend.Transport(tropts)
if err != nil {
return nil, err
}
// wrap the transport so that the throughput via HTTP is limited
lim := limiter.NewStaticLimiter(gopts.Limits)
lim := limiter.NewStaticLimiter(gopts.LimitUploadKb, gopts.LimitDownloadKb)
rt = lim.Transport(rt)
switch loc.Scheme {
@@ -734,7 +718,7 @@ func open(s string, gopts GlobalOptions, opts options.Options) (restic.Backend,
}
if err != nil {
return nil, errors.Fatalf("unable to open repository at %v: %v", location.StripPassword(s), err)
return nil, errors.Fatalf("unable to open repo at %v: %v", location.StripPassword(s), err)
}
// wrap backend if a test specified an inner hook
@@ -776,7 +760,12 @@ func create(s string, opts options.Options) (restic.Backend, error) {
return nil, err
}
rt, err := backend.Transport(globalOptions.TransportOptions)
tropts := backend.TransportOptions{
RootCertFilenames: globalOptions.CACerts,
TLSClientCertKeyFilename: globalOptions.TLSClientCert,
InsecureTLS: globalOptions.InsecureTLS,
}
rt, err := backend.Transport(tropts)
if err != nil {
return nil, err
}

View File

@@ -1,4 +1,3 @@
//go:build debug || profile
// +build debug profile
package main

View File

@@ -1,4 +1,3 @@
//go:build !debug && !profile
// +build !debug,!profile
package main

View File

@@ -1,106 +0,0 @@
//go:build go1.16
// +build go1.16
// Before Go 1.16 filepath.Match returned early on a failed match,
// and thus did not report any later syntax error in the pattern.
// https://go.dev/doc/go1.16#path/filepath
package main
import (
"io/ioutil"
"path/filepath"
"testing"
rtest "github.com/restic/restic/internal/test"
)
func TestBackupFailsWhenUsingInvalidPatterns(t *testing.T) {
env, cleanup := withTestEnvironment(t)
defer cleanup()
testRunInit(t, env.gopts)
var err error
// Test --exclude
err = testRunBackupAssumeFailure(t, filepath.Dir(env.testdata), []string{"testdata"}, BackupOptions{Excludes: []string{"*[._]log[.-][0-9]", "!*[._]log[.-][0-9]"}}, env.gopts)
rtest.Equals(t, `Fatal: --exclude: invalid pattern(s) provided:
*[._]log[.-][0-9]
!*[._]log[.-][0-9]`, err.Error())
// Test --iexclude
err = testRunBackupAssumeFailure(t, filepath.Dir(env.testdata), []string{"testdata"}, BackupOptions{InsensitiveExcludes: []string{"*[._]log[.-][0-9]", "!*[._]log[.-][0-9]"}}, env.gopts)
rtest.Equals(t, `Fatal: --iexclude: invalid pattern(s) provided:
*[._]log[.-][0-9]
!*[._]log[.-][0-9]`, err.Error())
}
func TestBackupFailsWhenUsingInvalidPatternsFromFile(t *testing.T) {
env, cleanup := withTestEnvironment(t)
defer cleanup()
testRunInit(t, env.gopts)
// Create an exclude file with some invalid patterns
excludeFile := env.base + "/excludefile"
fileErr := ioutil.WriteFile(excludeFile, []byte("*.go\n*[._]log[.-][0-9]\n!*[._]log[.-][0-9]"), 0644)
if fileErr != nil {
t.Fatalf("Could not write exclude file: %v", fileErr)
}
var err error
// Test --exclude-file:
err = testRunBackupAssumeFailure(t, filepath.Dir(env.testdata), []string{"testdata"}, BackupOptions{ExcludeFiles: []string{excludeFile}}, env.gopts)
rtest.Equals(t, `Fatal: --exclude-file: invalid pattern(s) provided:
*[._]log[.-][0-9]
!*[._]log[.-][0-9]`, err.Error())
// Test --iexclude-file
err = testRunBackupAssumeFailure(t, filepath.Dir(env.testdata), []string{"testdata"}, BackupOptions{InsensitiveExcludeFiles: []string{excludeFile}}, env.gopts)
rtest.Equals(t, `Fatal: --iexclude-file: invalid pattern(s) provided:
*[._]log[.-][0-9]
!*[._]log[.-][0-9]`, err.Error())
}
func TestRestoreFailsWhenUsingInvalidPatterns(t *testing.T) {
env, cleanup := withTestEnvironment(t)
defer cleanup()
testRunInit(t, env.gopts)
var err error
// Test --exclude
err = testRunRestoreAssumeFailure(t, "latest", RestoreOptions{Exclude: []string{"*[._]log[.-][0-9]", "!*[._]log[.-][0-9]"}}, env.gopts)
rtest.Equals(t, `Fatal: --exclude: invalid pattern(s) provided:
*[._]log[.-][0-9]
!*[._]log[.-][0-9]`, err.Error())
// Test --iexclude
err = testRunRestoreAssumeFailure(t, "latest", RestoreOptions{InsensitiveExclude: []string{"*[._]log[.-][0-9]", "!*[._]log[.-][0-9]"}}, env.gopts)
rtest.Equals(t, `Fatal: --iexclude: invalid pattern(s) provided:
*[._]log[.-][0-9]
!*[._]log[.-][0-9]`, err.Error())
// Test --include
err = testRunRestoreAssumeFailure(t, "latest", RestoreOptions{Include: []string{"*[._]log[.-][0-9]", "!*[._]log[.-][0-9]"}}, env.gopts)
rtest.Equals(t, `Fatal: --include: invalid pattern(s) provided:
*[._]log[.-][0-9]
!*[._]log[.-][0-9]`, err.Error())
// Test --iinclude
err = testRunRestoreAssumeFailure(t, "latest", RestoreOptions{InsensitiveInclude: []string{"*[._]log[.-][0-9]", "!*[._]log[.-][0-9]"}}, env.gopts)
rtest.Equals(t, `Fatal: --iinclude: invalid pattern(s) provided:
*[._]log[.-][0-9]
!*[._]log[.-][0-9]`, err.Error())
}

View File

@@ -1,4 +1,3 @@
//go:build darwin || freebsd || linux
// +build darwin freebsd linux
package main
@@ -55,7 +54,7 @@ func waitForMount(t testing.TB, dir string) {
func testRunMount(t testing.TB, gopts GlobalOptions, dir string) {
opts := MountOptions{
TimeTemplate: time.RFC3339,
SnapshotTemplate: time.RFC3339,
}
rtest.OK(t, runMount(opts, gopts, []string{dir}))
}
@@ -154,8 +153,6 @@ func TestMount(t *testing.T) {
}
env, cleanup := withTestEnvironment(t)
// must list snapshots more than once
env.gopts.backendTestHook = nil
defer cleanup()
testRunInit(t, env.gopts)
@@ -199,8 +196,6 @@ func TestMountSameTimestamps(t *testing.T) {
}
env, cleanup := withTestEnvironment(t)
// must list snapshots more than once
env.gopts.backendTestHook = nil
defer cleanup()
rtest.SetupTarTestFixture(t, env.base, filepath.Join("testdata", "repo-same-timestamps.tar.gz"))

View File

@@ -198,9 +198,6 @@ func withTestEnvironment(t testing.TB) (env *testEnvironment, cleanup func()) {
stdout: os.Stdout,
stderr: os.Stderr,
extended: make(options.Options),
// replace this hook with "nil" if listing a filetype more than once is necessary
backendTestHook: func(r restic.Backend) (restic.Backend, error) { return newOrderedListOnceBackend(r), nil },
}
// always overwrite global options

View File

@@ -1,5 +1,4 @@
//go:build !windows
// +build !windows
//+build !windows
package main

View File

@@ -1,5 +1,4 @@
//go:build windows
// +build windows
//+build windows
package main

View File

@@ -131,12 +131,6 @@ func testRunRestoreIncludes(t testing.TB, gopts GlobalOptions, dir string, snaps
rtest.OK(t, runRestore(opts, gopts, []string{snapshotID.String()}))
}
func testRunRestoreAssumeFailure(t testing.TB, snapshotID string, opts RestoreOptions, gopts GlobalOptions) error {
err := runRestore(opts, gopts, []string{snapshotID})
return err
}
func testRunCheck(t testing.TB, gopts GlobalOptions) {
opts := CheckOptions{
ReadData: true,
@@ -281,11 +275,6 @@ func testRunForgetJSON(t testing.TB, gopts GlobalOptions, args ...string) {
}
func testRunPrune(t testing.TB, gopts GlobalOptions, opts PruneOptions) {
oldHook := gopts.backendTestHook
gopts.backendTestHook = func(r restic.Backend) (restic.Backend, error) { return newListOnceBackend(r), nil }
defer func() {
gopts.backendTestHook = oldHook
}()
rtest.OK(t, runPrune(opts, gopts))
}
@@ -749,17 +738,14 @@ func TestBackupTags(t *testing.T) {
}
func testRunCopy(t testing.TB, srcGopts GlobalOptions, dstGopts GlobalOptions) {
gopts := srcGopts
gopts.Repo = dstGopts.Repo
gopts.password = dstGopts.password
copyOpts := CopyOptions{
secondaryRepoOptions: secondaryRepoOptions{
Repo: srcGopts.Repo,
password: srcGopts.password,
Repo: dstGopts.Repo,
password: dstGopts.password,
},
}
rtest.OK(t, runCopy(copyOpts, gopts, nil))
rtest.OK(t, runCopy(copyOpts, srcGopts, nil))
}
func TestCopy(t *testing.T) {
@@ -1049,7 +1035,7 @@ func testRunKeyAddNewKeyUserHost(t testing.TB, gopts GlobalOptions) {
repo, err := OpenRepository(gopts)
rtest.OK(t, err)
key, err := repository.SearchKey(gopts.ctx, repo, testKeyNewPassword, 2, "")
key, err := repository.SearchKey(gopts.ctx, repo, testKeyNewPassword, 1, "")
rtest.OK(t, err)
rtest.Equals(t, "john", key.Username)
@@ -1079,8 +1065,6 @@ func TestKeyAddRemove(t *testing.T) {
}
env, cleanup := withTestEnvironment(t)
// must list keys more than once
env.gopts.backendTestHook = nil
defer cleanup()
testRunInit(t, env.gopts)
@@ -1479,7 +1463,7 @@ func TestRebuildIndexAlwaysFull(t *testing.T) {
defer func() {
repository.IndexFull = indexFull
}()
repository.IndexFull = func(*repository.Index, bool) bool { return true }
repository.IndexFull = func(*repository.Index) bool { return true }
testRebuildIndex(t, nil)
}
@@ -1582,43 +1566,29 @@ func TestCheckRestoreNoLock(t *testing.T) {
}
func TestPrune(t *testing.T) {
testPruneVariants(t, false)
testPruneVariants(t, true)
}
func testPruneVariants(t *testing.T, unsafeNoSpaceRecovery bool) {
suffix := ""
if unsafeNoSpaceRecovery {
suffix = "-recovery"
}
t.Run("0"+suffix, func(t *testing.T) {
opts := PruneOptions{MaxUnused: "0%", unsafeRecovery: unsafeNoSpaceRecovery}
t.Run("0", func(t *testing.T) {
opts := PruneOptions{MaxUnused: "0%"}
checkOpts := CheckOptions{ReadData: true, CheckUnused: true}
testPrune(t, opts, checkOpts)
})
t.Run("50"+suffix, func(t *testing.T) {
opts := PruneOptions{MaxUnused: "50%", unsafeRecovery: unsafeNoSpaceRecovery}
t.Run("50", func(t *testing.T) {
opts := PruneOptions{MaxUnused: "50%"}
checkOpts := CheckOptions{ReadData: true}
testPrune(t, opts, checkOpts)
})
t.Run("unlimited"+suffix, func(t *testing.T) {
opts := PruneOptions{MaxUnused: "unlimited", unsafeRecovery: unsafeNoSpaceRecovery}
t.Run("unlimited", func(t *testing.T) {
opts := PruneOptions{MaxUnused: "unlimited"}
checkOpts := CheckOptions{ReadData: true}
testPrune(t, opts, checkOpts)
})
t.Run("CachableOnly"+suffix, func(t *testing.T) {
opts := PruneOptions{MaxUnused: "5%", RepackCachableOnly: true, unsafeRecovery: unsafeNoSpaceRecovery}
t.Run("CachableOnly", func(t *testing.T) {
opts := PruneOptions{MaxUnused: "5%", RepackCachableOnly: true}
checkOpts := CheckOptions{ReadData: true}
testPrune(t, opts, checkOpts)
})
t.Run("Small", func(t *testing.T) {
opts := PruneOptions{MaxUnused: "unlimited", RepackSmall: true}
checkOpts := CheckOptions{ReadData: true, CheckUnused: true}
testPrune(t, opts, checkOpts)
})
}
func testPrune(t *testing.T, pruneOpts PruneOptions, checkOpts CheckOptions) {
@@ -1689,11 +1659,6 @@ func TestPruneWithDamagedRepository(t *testing.T) {
rtest.Assert(t, len(snapshotIDs) == 1,
"expected one snapshot, got %v", snapshotIDs)
oldHook := env.gopts.backendTestHook
env.gopts.backendTestHook = func(r restic.Backend) (restic.Backend, error) { return newListOnceBackend(r), nil }
defer func() {
env.gopts.backendTestHook = oldHook
}()
// prune should fail
rtest.Assert(t, runPrune(pruneDefaultOptions, env.gopts) == errorPacksMissing,
"prune should have reported index not complete error")
@@ -1787,22 +1752,12 @@ func testEdgeCaseRepo(t *testing.T, tarfile string, optionsCheck CheckOptions, o
type listOnceBackend struct {
restic.Backend
listedFileType map[restic.FileType]bool
strictOrder bool
}
func newListOnceBackend(be restic.Backend) *listOnceBackend {
return &listOnceBackend{
Backend: be,
listedFileType: make(map[restic.FileType]bool),
strictOrder: false,
}
}
func newOrderedListOnceBackend(be restic.Backend) *listOnceBackend {
return &listOnceBackend{
Backend: be,
listedFileType: make(map[restic.FileType]bool),
strictOrder: true,
}
}
@@ -1810,9 +1765,6 @@ func (be *listOnceBackend) List(ctx context.Context, t restic.FileType, fn func(
if t != restic.LockFile && be.listedFileType[t] {
return errors.Errorf("tried listing type %v the second time", t)
}
if be.strictOrder && t == restic.SnapshotFile && be.listedFileType[restic.IndexFile] {
return errors.Errorf("tried listing type snapshots after index")
}
be.listedFileType[t] = true
return be.Backend.List(ctx, t, fn)
}
@@ -2183,38 +2135,7 @@ func TestBackendLoadWriteTo(t *testing.T) {
firstSnapshot := testRunList(t, "snapshots", env.gopts)
rtest.Assert(t, len(firstSnapshot) == 1,
"expected one snapshot, got %v", firstSnapshot)
}
func TestFindListOnce(t *testing.T) {
env, cleanup := withTestEnvironment(t)
defer cleanup()
env.gopts.backendTestHook = func(r restic.Backend) (restic.Backend, error) {
return newListOnceBackend(r), nil
}
testSetupBackupData(t, env)
opts := BackupOptions{}
testRunBackup(t, "", []string{filepath.Join(env.testdata, "0", "0", "9")}, opts, env.gopts)
testRunBackup(t, "", []string{filepath.Join(env.testdata, "0", "0", "9", "2")}, opts, env.gopts)
secondSnapshot := testRunList(t, "snapshots", env.gopts)
testRunBackup(t, "", []string{filepath.Join(env.testdata, "0", "0", "9", "3")}, opts, env.gopts)
thirdSnapshot := restic.NewIDSet(testRunList(t, "snapshots", env.gopts)...)
repo, err := OpenRepository(env.gopts)
rtest.OK(t, err)
snapshotIDs := restic.NewIDSet()
// specify the two oldest snapshots explicitly and use "latest" to reference the newest one
for sn := range FindFilteredSnapshots(context.TODO(), repo.Backend(), repo, nil, nil, nil, []string{
secondSnapshot[0].String(),
secondSnapshot[1].String()[:8],
"latest",
}) {
snapshotIDs.Insert(*sn.ID())
}
// the snapshots can only be listed once, if both lists match then the there has been only a single List() call
rtest.Equals(t, thirdSnapshot, snapshotIDs)
// test readData using the hashing.Reader
testRunCheck(t, env.gopts)
}

View File

@@ -98,11 +98,11 @@ func main() {
err := cmdRoot.Execute()
switch {
case restic.IsAlreadyLocked(err):
case restic.IsAlreadyLocked(errors.Cause(err)):
fmt.Fprintf(os.Stderr, "%v\nthe `unlock` command can be used to remove stale locks\n", err)
case err == ErrInvalidSourceData:
fmt.Fprintf(os.Stderr, "Warning: %v\n", err)
case errors.IsFatal(err):
case errors.IsFatal(errors.Cause(err)):
fmt.Fprintf(os.Stderr, "%v\n", err)
case err != nil:
fmt.Fprintf(os.Stderr, "%+v\n", err)

View File

@@ -58,10 +58,7 @@ func printProgress(status string, canUpdateStatus bool) {
if w < 3 {
status = termstatus.Truncate(status, w)
} else {
trunc := termstatus.Truncate(status, w-3)
if len(trunc) < len(status) {
status = trunc + "..."
}
status = termstatus.Truncate(status, w-3) + "..."
}
}

View File

@@ -8,98 +8,49 @@ import (
)
type secondaryRepoOptions struct {
password string
// from-repo options
Repo string
RepositoryFile string
password string
PasswordFile string
PasswordCommand string
KeyHint string
// repo2 options
LegacyRepo string
LegacyRepositoryFile string
LegacyPasswordFile string
LegacyPasswordCommand string
LegacyKeyHint string
}
func initSecondaryRepoOptions(f *pflag.FlagSet, opts *secondaryRepoOptions, repoPrefix string, repoUsage string) {
f.StringVarP(&opts.LegacyRepo, "repo2", "", os.Getenv("RESTIC_REPOSITORY2"), repoPrefix+" `repository` "+repoUsage+" (default: $RESTIC_REPOSITORY2)")
f.StringVarP(&opts.LegacyRepositoryFile, "repository-file2", "", os.Getenv("RESTIC_REPOSITORY_FILE2"), "`file` from which to read the "+repoPrefix+" repository location "+repoUsage+" (default: $RESTIC_REPOSITORY_FILE2)")
f.StringVarP(&opts.LegacyPasswordFile, "password-file2", "", os.Getenv("RESTIC_PASSWORD_FILE2"), "`file` to read the "+repoPrefix+" repository password from (default: $RESTIC_PASSWORD_FILE2)")
f.StringVarP(&opts.LegacyKeyHint, "key-hint2", "", os.Getenv("RESTIC_KEY_HINT2"), "key ID of key to try decrypting the "+repoPrefix+" repository first (default: $RESTIC_KEY_HINT2)")
f.StringVarP(&opts.LegacyPasswordCommand, "password-command2", "", os.Getenv("RESTIC_PASSWORD_COMMAND2"), "shell `command` to obtain the "+repoPrefix+" repository password from (default: $RESTIC_PASSWORD_COMMAND2)")
// hide repo2 options
_ = f.MarkDeprecated("repo2", "use --repo or --from-repo instead")
_ = f.MarkDeprecated("repository-file2", "use --repository-file or --from-repository-file instead")
_ = f.MarkHidden("password-file2")
_ = f.MarkHidden("key-hint2")
_ = f.MarkHidden("password-command2")
f.StringVarP(&opts.Repo, "from-repo", "", os.Getenv("RESTIC_FROM_REPOSITORY"), "source `repository` "+repoUsage+" (default: $RESTIC_FROM_REPOSITORY)")
f.StringVarP(&opts.RepositoryFile, "from-repository-file", "", os.Getenv("RESTIC_FROM_REPOSITORY_FILE"), "`file` from which to read the source repository location "+repoUsage+" (default: $RESTIC_FROM_REPOSITORY_FILE)")
f.StringVarP(&opts.PasswordFile, "from-password-file", "", os.Getenv("RESTIC_FROM_PASSWORD_FILE"), "`file` to read the source repository password from (default: $RESTIC_FROM_PASSWORD_FILE)")
f.StringVarP(&opts.KeyHint, "from-key-hint", "", os.Getenv("RESTIC_FROM_KEY_HINT"), "key ID of key to try decrypting the source repository first (default: $RESTIC_FROM_KEY_HINT)")
f.StringVarP(&opts.PasswordCommand, "from-password-command", "", os.Getenv("RESTIC_FROM_PASSWORD_COMMAND"), "shell `command` to obtain the source repository password from (default: $RESTIC_FROM_PASSWORD_COMMAND)")
f.StringVarP(&opts.Repo, "repo2", "", os.Getenv("RESTIC_REPOSITORY2"), repoPrefix+" `repository` "+repoUsage+" (default: $RESTIC_REPOSITORY2)")
f.StringVarP(&opts.RepositoryFile, "repository-file2", "", os.Getenv("RESTIC_REPOSITORY_FILE2"), "`file` from which to read the "+repoPrefix+" repository location "+repoUsage+" (default: $RESTIC_REPOSITORY_FILE2)")
f.StringVarP(&opts.PasswordFile, "password-file2", "", os.Getenv("RESTIC_PASSWORD_FILE2"), "`file` to read the "+repoPrefix+" repository password from (default: $RESTIC_PASSWORD_FILE2)")
f.StringVarP(&opts.KeyHint, "key-hint2", "", os.Getenv("RESTIC_KEY_HINT2"), "key ID of key to try decrypting the "+repoPrefix+" repository first (default: $RESTIC_KEY_HINT2)")
f.StringVarP(&opts.PasswordCommand, "password-command2", "", os.Getenv("RESTIC_PASSWORD_COMMAND2"), "shell `command` to obtain the "+repoPrefix+" repository password from (default: $RESTIC_PASSWORD_COMMAND2)")
}
func fillSecondaryGlobalOpts(opts secondaryRepoOptions, gopts GlobalOptions, repoPrefix string) (GlobalOptions, bool, error) {
if opts.Repo == "" && opts.RepositoryFile == "" && opts.LegacyRepo == "" && opts.LegacyRepositoryFile == "" {
return GlobalOptions{}, false, errors.Fatal("Please specify a source repository location (--from-repo or --from-repository-file)")
func fillSecondaryGlobalOpts(opts secondaryRepoOptions, gopts GlobalOptions, repoPrefix string) (GlobalOptions, error) {
if opts.Repo == "" && opts.RepositoryFile == "" {
return GlobalOptions{}, errors.Fatal("Please specify a " + repoPrefix + " repository location (--repo2 or --repository-file2)")
}
hasFromRepo := opts.Repo != "" || opts.RepositoryFile != "" || opts.PasswordFile != "" ||
opts.KeyHint != "" || opts.PasswordCommand != ""
hasRepo2 := opts.LegacyRepo != "" || opts.LegacyRepositoryFile != "" || opts.LegacyPasswordFile != "" ||
opts.LegacyKeyHint != "" || opts.LegacyPasswordCommand != ""
if hasFromRepo && hasRepo2 {
return GlobalOptions{}, false, errors.Fatal("Option groups repo2 and from-repo are mutually exclusive, please specify only one")
if opts.Repo != "" && opts.RepositoryFile != "" {
return GlobalOptions{}, errors.Fatal("Options --repo2 and --repository-file2 are mutually exclusive, please specify only one")
}
var err error
dstGopts := gopts
var pwdEnv string
if hasFromRepo {
if opts.Repo != "" && opts.RepositoryFile != "" {
return GlobalOptions{}, false, errors.Fatal("Options --from-repo and --from-repository-file are mutually exclusive, please specify only one")
}
dstGopts.Repo = opts.Repo
dstGopts.RepositoryFile = opts.RepositoryFile
dstGopts.PasswordFile = opts.PasswordFile
dstGopts.PasswordCommand = opts.PasswordCommand
dstGopts.KeyHint = opts.KeyHint
pwdEnv = "RESTIC_FROM_PASSWORD"
repoPrefix = "source"
} else {
if opts.LegacyRepo != "" && opts.LegacyRepositoryFile != "" {
return GlobalOptions{}, false, errors.Fatal("Options --repo2 and --repository-file2 are mutually exclusive, please specify only one")
}
dstGopts.Repo = opts.LegacyRepo
dstGopts.RepositoryFile = opts.LegacyRepositoryFile
dstGopts.PasswordFile = opts.LegacyPasswordFile
dstGopts.PasswordCommand = opts.LegacyPasswordCommand
dstGopts.KeyHint = opts.LegacyKeyHint
pwdEnv = "RESTIC_PASSWORD2"
}
dstGopts.Repo = opts.Repo
dstGopts.RepositoryFile = opts.RepositoryFile
dstGopts.PasswordFile = opts.PasswordFile
dstGopts.PasswordCommand = opts.PasswordCommand
dstGopts.KeyHint = opts.KeyHint
if opts.password != "" {
dstGopts.password = opts.password
} else {
dstGopts.password, err = resolvePassword(dstGopts, pwdEnv)
dstGopts.password, err = resolvePassword(dstGopts, "RESTIC_PASSWORD2")
if err != nil {
return GlobalOptions{}, false, err
return GlobalOptions{}, err
}
}
dstGopts.password, err = ReadPassword(dstGopts, "enter password for "+repoPrefix+" repository: ")
if err != nil {
return GlobalOptions{}, false, err
return GlobalOptions{}, err
}
return dstGopts, hasFromRepo, nil
return dstGopts, nil
}

View File

@@ -8,13 +8,12 @@ import (
rtest "github.com/restic/restic/internal/test"
)
// TestFillSecondaryGlobalOpts tests valid and invalid data on fillSecondaryGlobalOpts-function
//TestFillSecondaryGlobalOpts tests valid and invalid data on fillSecondaryGlobalOpts-function
func TestFillSecondaryGlobalOpts(t *testing.T) {
//secondaryRepoTestCase defines a struct for test cases
type secondaryRepoTestCase struct {
Opts secondaryRepoOptions
DstGOpts GlobalOptions
FromRepo bool
}
//validSecondaryRepoTestCases is a list with test cases that must pass
@@ -29,7 +28,6 @@ func TestFillSecondaryGlobalOpts(t *testing.T) {
Repo: "backupDst",
password: "secretDst",
},
FromRepo: true,
},
{
// Test if RepositoryFile and PasswordFile are parsed correctly.
@@ -42,7 +40,6 @@ func TestFillSecondaryGlobalOpts(t *testing.T) {
password: "secretDst",
PasswordFile: "passwordFileDst",
},
FromRepo: true,
},
{
// Test if RepositoryFile and PasswordCommand are parsed correctly.
@@ -55,42 +52,6 @@ func TestFillSecondaryGlobalOpts(t *testing.T) {
password: "secretDst",
PasswordCommand: "echo secretDst",
},
FromRepo: true,
},
{
// Test if LegacyRepo and Password are parsed correctly.
Opts: secondaryRepoOptions{
LegacyRepo: "backupDst",
password: "secretDst",
},
DstGOpts: GlobalOptions{
Repo: "backupDst",
password: "secretDst",
},
},
{
// Test if LegacyRepositoryFile and LegacyPasswordFile are parsed correctly.
Opts: secondaryRepoOptions{
LegacyRepositoryFile: "backupDst",
LegacyPasswordFile: "passwordFileDst",
},
DstGOpts: GlobalOptions{
RepositoryFile: "backupDst",
password: "secretDst",
PasswordFile: "passwordFileDst",
},
},
{
// Test if LegacyRepositoryFile and LegacyPasswordCommand are parsed correctly.
Opts: secondaryRepoOptions{
LegacyRepositoryFile: "backupDst",
LegacyPasswordCommand: "echo secretDst",
},
DstGOpts: GlobalOptions{
RepositoryFile: "backupDst",
password: "secretDst",
PasswordCommand: "echo secretDst",
},
},
}
@@ -135,20 +96,6 @@ func TestFillSecondaryGlobalOpts(t *testing.T) {
Repo: "backupDst",
},
},
{
// Test must fail as current and legacy options are mixed
Opts: secondaryRepoOptions{
Repo: "backupDst",
LegacyRepo: "backupDst",
},
},
{
// Test must fail as current and legacy options are mixed
Opts: secondaryRepoOptions{
Repo: "backupDst",
LegacyPasswordCommand: "notEmpty",
},
},
}
//gOpts defines the Global options used in the secondary repository tests
@@ -172,15 +119,14 @@ func TestFillSecondaryGlobalOpts(t *testing.T) {
// Test all valid cases
for _, testCase := range validSecondaryRepoTestCases {
DstGOpts, isFromRepo, err := fillSecondaryGlobalOpts(testCase.Opts, gOpts, "destination")
DstGOpts, err := fillSecondaryGlobalOpts(testCase.Opts, gOpts, "destination")
rtest.OK(t, err)
rtest.Equals(t, DstGOpts, testCase.DstGOpts)
rtest.Equals(t, isFromRepo, testCase.FromRepo)
}
// Test all invalid cases
for _, testCase := range invalidSecondaryRepoTestCases {
_, _, err := fillSecondaryGlobalOpts(testCase.Opts, gOpts, "destination")
_, err := fillSecondaryGlobalOpts(testCase.Opts, gOpts, "destination")
rtest.Assert(t, err != nil, "Expected error, but function did not return an error")
}
}

2
doc.go
View File

@@ -1,6 +1,6 @@
// Package restic gives a (very brief) introduction to the structure of source code.
//
// # Overview
// Overview
//
// The packages are structured so that cmd/ contains the main package for the
// restic binary, and internal/ contains almost all code in library form. We've

View File

@@ -274,7 +274,7 @@ From Source
***********
restic is written in the Go programming language and you need at least
Go version 1.15. Building restic may also work with older versions of Go,
Go version 1.14. Building restic may also work with older versions of Go,
but that's not supported. See the `Getting
started <https://golang.org/doc/install>`__ guide of the Go project for
instructions how to install Go.
@@ -339,13 +339,6 @@ Example for using sudo to write a bash completion script directly to the system-
$ sudo ./restic generate --bash-completion /etc/bash_completion.d/restic
writing bash completion file to /etc/bash_completion.d/restic
Example for using sudo to write a zsh completion script directly to the system-wide location:
.. code-block:: console
$ sudo ./restic generate --zsh-completion /usr/local/share/zsh/site-functions/_restic
writing zsh completion file to /usr/local/share/zsh/site-functions/_restic
.. note:: The path for the ``--bash-completion`` option may vary depending on
the operating system used, e.g. ``/usr/share/bash-completion/completions/restic``
in Debian and derivatives. Please look up the correct path in the appropriate

View File

@@ -14,26 +14,18 @@
Preparing a new repository
##########################
The place where your backups will be saved is called a "repository". This is
simply a directory containing a set of subdirectories and files created by
restic to store your backups, some corresponding metadata and encryption keys.
To access the repository, a password (also called a key) must be specified. A
repository can hold multiple keys that can all be used to access the repository.
The place where your backups will be saved is called a "repository".
This chapter explains how to create ("init") such a repository. The repository
can be stored locally, or on some remote server or service. We'll first cover
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 supports specifying the repository location in the
For automated backups, restic accepts the repository location in the
environment variable ``RESTIC_REPOSITORY``. Restic can also read the repository
location from a file specified via the ``--repository-file`` option or the
environment variable ``RESTIC_REPOSITORY_FILE``.
For automating the supply of the repository password to restic, several options
exist:
environment variable ``RESTIC_REPOSITORY_FILE``. For the password, several
options exist:
* Setting the environment variable ``RESTIC_PASSWORD``
@@ -43,26 +35,6 @@ exist:
* Configuring a program to be called when the password is needed via the
option ``--password-command`` or the environment variable
``RESTIC_PASSWORD_COMMAND``
The ``init`` command has an option called ``--repository-version`` which can
be used to explicitly set the version of the new repository. By default, the
current stable version is used (see table below). The alias ``latest`` will
always resolve to the latest repository version. Have a look at the `design
documentation <https://github.com/restic/restic/blob/master/doc/design.rst>`__
for more details.
The below table shows which restic version is required to use a certain
repository version, as well as notable features introduced in the various
versions.
+--------------------+-------------------------+---------------------+------------------+
| Repository version | Required restic version | Major new features | Comment |
+====================+=========================+=====================+==================+
| ``1`` | Any | | Current default |
+--------------------+-------------------------+---------------------+------------------+
| ``2`` | 0.14.0 or newer | Compression support | |
+--------------------+-------------------------+---------------------+------------------+
Local
*****
@@ -95,9 +67,9 @@ SFTP
****
In order to backup data via SFTP, you must first set up a server with
SSH and let it know your public key. Passwordless login is important
since automatic backups are not possible if the server prompts for
credentials.
SSH and let it know your public key. Passwordless login is really
important since restic fails to connect to the repository if the server
prompts for credentials.
Once the server is configured, the setup of the SFTP repository can
simply be achieved by changing the URL scheme in the ``init`` command:
@@ -176,7 +148,7 @@ SFTP connection, you can specify the command to be run with the option
.. note:: Please be aware that sftp servers close connections when no data is
received by the client. This can happen when restic is processing huge
amounts of unchanged data. To avoid this issue add the following lines
to the client's .ssh/config file:
to the clients .ssh/config file:
::
@@ -195,7 +167,7 @@ scheme like this:
.. code-block:: console
$ restic -r rest:http://host:8000/ init
$ restic -r rest:http://host:8000/
Depending on your REST server setup, you can use HTTPS protocol,
password protection, multiple repositories or any combination of
@@ -204,9 +176,9 @@ are some more examples:
.. code-block:: console
$ restic -r rest:https://host:8000/ init
$ restic -r rest:https://user:pass@host:8000/ init
$ restic -r rest:https://user:pass@host:8000/my_backup_repo/ init
$ restic -r rest:https://host:8000/
$ restic -r rest:https://user:pass@host:8000/
$ restic -r rest:https://user:pass@host:8000/my_backup_repo/
If you use TLS, restic will use the system's CA certificates to verify the
server certificate. When the verification fails, restic refuses to proceed and
@@ -254,9 +226,6 @@ parameter like ``-o s3.region="us-east-1"``. If the region is not specified,
the default region is used. Afterwards, the S3 server (at least for AWS,
``s3.amazonaws.com``) will redirect restic to the correct endpoint.
When using temporary credentials make sure to include the session token via
then environment variable ``AWS_SESSION_TOKEN``.
Until version 0.8.0, restic used a default prefix of ``restic``, so the files
in the bucket were placed in a directory named ``restic``. If you want to
access a repository created with an older version of restic, specify the path
@@ -513,13 +482,6 @@ account name and key as follows:
$ export AZURE_ACCOUNT_NAME=<ACCOUNT_NAME>
$ export AZURE_ACCOUNT_KEY=<SECRET_KEY>
or
.. code-block:: console
$ export AZURE_ACCOUNT_NAME=<ACCOUNT_NAME>
$ export AZURE_ACCOUNT_SAS=<SAS_TOKEN>
Afterwards you can initialize a repository in a container called ``foo`` in the
root path like this:
@@ -539,10 +501,6 @@ established.
Google Cloud Storage
********************
.. note:: Google Cloud Storage is not the same service as Google Drive - to use
the latter, please see :ref:`other-services` for instructions on using
the rclone backend.
Restic supports Google Cloud Storage as a backend and connects via a `service account`_.
For normal restic operation, the service account must have the
@@ -589,18 +547,16 @@ repository in the bucket ``foo`` at the root path:
enter password for new repository:
enter password again:
created restic repository bde47d6254 at gs:foo/
created restic repository bde47d6254 at gs:foo2/
[...]
The number of concurrent connections to the GCS service can be set with the
``-o gs.connections=10`` switch. By default, at most five parallel connections are
established.
.. _service account: https://cloud.google.com/iam/docs/service-accounts
.. _create a service account key: https://cloud.google.com/iam/docs/creating-managing-service-account-keys#iam-service-account-keys-create-console
.. _default authentication material: https://cloud.google.com/docs/authentication/production
.. _other-services:
.. _service account: https://cloud.google.com/storage/docs/authentication#service_accounts
.. _create a service account key: https://cloud.google.com/storage/docs/authentication#generating-a-private-key
.. _default authentication material: https://developers.google.com/identity/protocols/application-default-credentials
Other Services via rclone
*************************
@@ -610,7 +566,7 @@ store data there. First, you need to install and `configure`_ rclone. The
general backend specification format is ``rclone:<remote>:<path>``, the
``<remote>:<path>`` component will be directly passed to rclone. When you
configure a remote named ``foo``, you can then call restic as follows to
initiate a new repository in the path ``bar`` in the remote ``foo``:
initiate a new repository in the path ``bar`` in the repo:
.. code-block:: console
@@ -737,55 +693,3 @@ On MSYS2, you can install ``winpty`` as follows:
$ pacman -S winpty
$ winpty restic -r /srv/restic-repo init
Group accessible repositories
*****************************
Since restic version 0.14 local and SFTP repositories can be made
accessible to members of a system group. To control this we have to change
the group permissions of the top-level ``config`` file and restic will use
this as a hint to determine what permissions to apply to newly created
files. By default ``restic init`` sets repositories up to be group
inaccessible.
In order to give group members read-only access we simply add the read
permission bit to all repository files with ``chmod``:
.. code-block:: console
$ chmod -R g+r /srv/restic-repo
This serves two purposes: 1) it sets the read permission bit on the
repository config file triggering restic's logic to create new files as
group accessible and 2) it actually allows the group read access to the
files.
.. note:: By default files on Unix systems are created with a user's
primary group as defined by the gid (group id) field in
``/etc/passwd``. See `passwd(5)
<https://manpages.debian.org/latest/passwd/passwd.5.en.html>`_.
For read-write access things are a bit more complicated. When users other
than the repository creator add new files in the repository they will be
group-owned by this user's primary group by default, not that of the
original repository owner, meaning the original creator wouldn't have
access to these files. That's hardly what you'd want.
To make this work we can employ the help of the ``setgid`` permission bit
available on Linux and most other Unix systems. This permission bit makes
newly created directories inherit both the group owner (gid) and setgid bit
from the parent directory. Setting this bit requires root but since it
propagates down to any new directories we only have to do this priviledged
setup once:
.. code-block:: console
# find /srv/restic-repo -type d -exec chmod g+s '{}' \;
$ chmod -R g+rw /srv/restic-repo
This sets the ``setgid`` bit on all existing directories in the repository
and then grants read/write permissions for group access.
.. note:: To manage who has access to the repository you can use
``usermod`` on Linux systems, to change which group controls
repository access ``chgrp -R`` is your friend.

View File

@@ -191,7 +191,7 @@ Dry Runs
********
You can perform a backup in dry run mode to see what would happen without
modifying the repository.
modifying the repo.
- ``--dry-run``/``-n`` Report what would be done, without writing to the repository
@@ -202,7 +202,7 @@ Combined with ``--verbose``, you can see a list of changes:
$ restic -r /srv/restic-repo backup ~/work --dry-run -vv | grep "added"
modified /plan.txt, saved in 0.000s (9.110 KiB added)
modified /archive.tar.gz, saved in 0.140s (25.542 MiB added)
Would be added to the repository: 25.551 MiB
Would be added to the repo: 25.551 MiB
Excluding Files
***************
@@ -212,7 +212,7 @@ the exclude options are:
- ``--exclude`` Specified one or more times to exclude one or more items
- ``--iexclude`` Same as ``--exclude`` but ignores the case of paths
- ``--exclude-caches`` Specified once to exclude folders containing `this special file <https://bford.info/cachedir/>`__
- ``--exclude-caches`` Specified once to exclude folders containing a special file
- ``--exclude-file`` Specified one or more times to exclude items listed in a given file
- ``--iexclude-file`` Same as ``exclude-file`` but ignores cases like in ``--iexclude``
- ``--exclude-if-present foo`` Specified one or more times to exclude a folder's content if it contains a file called ``foo`` (optionally having a given header, no wildcards for the file name supported)
@@ -346,12 +346,12 @@ option:
$ restic -r /srv/restic-repo backup ~/work --exclude-larger-than 1M
This excludes files in ``~/work`` which are larger than 1 MiB from the backup.
This excludes files in ``~/work`` which are larger than 1 MB from the backup.
The default unit for the size value is bytes, so e.g. ``--exclude-larger-than 2048``
would exclude files larger than 2048 bytes (2 KiB). To specify other units,
suffix the size value with one of ``k``/``K`` for KiB (1024 bytes), ``m``/``M`` for MiB (1024^2 bytes),
``g``/``G`` for GiB (1024^3 bytes) and ``t``/``T`` for TiB (1024^4 bytes), e.g. ``1k``, ``10K``, ``20m``,
would exclude files larger than 2048 bytes (2 kilobytes). To specify other units,
suffix the size value with one of ``k``/``K`` for kilobytes, ``m``/``M`` for megabytes,
``g``/``G`` for gigabytes and ``t``/``T`` for terabytes (e.g. ``1k``, ``10K``, ``20m``,
``20M``, ``30g``, ``30G``, ``2t`` or ``2T``).
Including Files
@@ -552,15 +552,12 @@ environment variables. The following lists these environment variables:
RESTIC_PASSWORD_COMMAND Command printing the password for the repository to stdout
RESTIC_KEY_HINT ID of key to try decrypting first, before other keys
RESTIC_CACHE_DIR Location of the cache directory
RESTIC_COMPRESSION Compression mode (only available for repository format version 2)
RESTIC_PROGRESS_FPS Frames per second by which the progress bar is updated
RESTIC_PACK_SIZE Target size for pack files
TMPDIR Location for temporary files
AWS_ACCESS_KEY_ID Amazon S3 access key ID
AWS_SECRET_ACCESS_KEY Amazon S3 secret access key
AWS_SESSION_TOKEN Amazon S3 temporary session token
AWS_DEFAULT_REGION Amazon S3 default region
AWS_PROFILE Amazon credentials profile (alternative to specifying key and region)
AWS_SHARED_CREDENTIALS_FILE Location of the AWS CLI shared credentials file (default: ~/.aws/credentials)
@@ -596,7 +593,6 @@ environment variables. The following lists these environment variables:
AZURE_ACCOUNT_NAME Account name for Azure
AZURE_ACCOUNT_KEY Account key for Azure
AZURE_ACCOUNT_SAS Shared access signatures (SAS) for Azure
GOOGLE_PROJECT_ID Project ID for Google Cloud Storage
GOOGLE_APPLICATION_CREDENTIALS Application Credentials for Google Cloud Storage (e.g. $HOME/.config/gs-secret-restic-key.json)

View File

@@ -90,7 +90,7 @@ example from a local to a remote repository, you can use the ``copy`` command:
.. code-block:: console
$ restic -r /srv/restic-repo-copy copy --from-repo /srv/restic-repo
$ restic -r /srv/restic-repo copy --repo2 /srv/restic-repo-copy
repository d6504c63 opened successfully, password is correct
repository 3dd0878c opened successfully, password is correct
@@ -117,17 +117,17 @@ be skipped by later copy runs.
both the source and destination repository, *may occupy up to twice their
space* in the destination repository. See below for how to avoid this.
The source repository is specified with ``--from-repo`` or can be read
from a file specified via ``--from-repository-file``. Both of these options
can also be set as environment variables ``$RESTIC_FROM_REPOSITORY`` or
``$RESTIC_FROM_REPOSITORY_FILE``, respectively. For the destination repository
the password can be read from a file ``--from-password-file`` or from a command
``--from-password-command``.
Alternatively the environment variables ``$RESTIC_FROM_PASSWORD_COMMAND`` and
``$RESTIC_FROM_PASSWORD_FILE`` can be used. It is also possible to directly
pass the password via ``$RESTIC_FROM_PASSWORD``. The key which should be used
for decryption can be selected by passing its ID via the flag ``--from-key-hint``
or the environment variable ``$RESTIC_FROM_KEY_HINT``.
The destination repository is specified with ``--repo2`` or can be read
from a file specified via ``--repository-file2``. Both of these options
can also set as environment variables ``$RESTIC_REPOSITORY2`` or
``$RESTIC_REPOSITORY_FILE2`` respectively. For the destination repository
the password can be read from a file ``--password-file2`` or from a command
``--password-command2``.
Alternatively the environment variables ``$RESTIC_PASSWORD_COMMAND2`` and
``$RESTIC_PASSWORD_FILE2`` can be used. It is also possible to directly
pass the password via ``$RESTIC_PASSWORD2``. The key which should be used
for decryption can be selected by passing its ID via the flag ``--key-hint2``
or the environment variable ``$RESTIC_KEY_HINT2``.
.. note:: In case the source and destination repository use the same backend,
the configuration options and environment variables used to configure the
@@ -298,29 +298,3 @@ a file size value the following command may be used:
$ restic -r /srv/restic-repo check --read-data-subset=50M
$ restic -r /srv/restic-repo check --read-data-subset=10G
Upgrading the repository format version
=======================================
Repositories created using earlier restic versions use an older repository
format version and have to be upgraded to allow using all new features.
Upgrading must be done explicitly as a newer repository version increases the
minimum restic version required to access the repository. For example the
repository format version 2 is only readable using restic 0.14.0 or newer.
Upgrading to repository version 2 is a two step process: first run
``migrate upgrade_repo_v2`` which will check the repository integrity and
then upgrade the repository version. Repository problems must be corrected
before the migration will be possible. After the migration is complete, run
``prune`` to compress the repository metadata. To limit the amount of data
rewritten in at once, you can use the ``prune --max-repack-size size``
parameter, see :ref:`customize-pruning` for more details.
File contents stored in the repository will not be rewritten, data from new
backups will be compressed. Over time more and more of the repository will
be compressed. To speed up this process and compress all not yet compressed
data, you can run ``prune --repack-uncompressed``. When you plan to create
your backups with maximum compression, you should also add the
``--compression max`` flag to the prune command. For already backed up data,
the compression level cannot be changed later on.

View File

@@ -1,80 +0,0 @@
..
Normally, there are no heading levels assigned to certain characters as the structure is
determined from the succession of headings. However, this convention is used in Pythons
Style Guide for documenting which you may follow:
# with overline, for parts
* for chapters
= for sections
- for subsections
^ for subsubsections
" for paragraphs
########################
Tuning Backup Parameters
########################
Restic offers a few parameters that allow tuning the backup. The default values should
work well in general although specific use cases can benefit from different non-default
values. As the restic commands evolve over time, the optimal value for each parameter
can also change across restic versions.
Backend Connections
===================
Restic uses a global limit for the number of concurrent connections to a backend.
This limit can be configured using ``-o <backend-name>.connections=5``, for example for
the REST backend the parameter would be ``-o rest.connections=5``. By default restic uses
``5`` connections for each backend, except for the local backend which uses a limit of ``2``.
The defaults should work well in most cases. For high-latency backends it can be beneficial
to increase the number of connections. Please be aware that this increases the resource
consumption of restic and that a too high connection count *will degrade performance*.
CPU Usage
=========
By default, restic uses all available CPU cores. You can set the environment variable
`GOMAXPROCS` to limit the number of used CPU cores. For example to use a single CPU core,
use `GOMAXPROCS=1`. Limiting the number of usable CPU cores, can slightly reduce the memory
usage of restic.
Compression
===========
For a repository using at least repository format version 2, you can configure how data
is compressed with the option ``--compression``. It can be set to ``auto`` (the default,
which will compress very fast), ``max`` (which will trade backup speed and CPU usage for
slightly better compression), or ``off`` (which disables compression). Each setting is
only applied for the single run of restic. The option can also be set via the environment
variable ``RESTIC_COMPRESSION``.
Pack Size
=========
In certain instances, such as very large repositories (in the TiB range) or very fast
upload connections, it is desirable to use larger pack sizes to reduce the number of
files in the repository and improve upload performance. Notable examples are OpenStack
Swift and some Google Drive Team accounts, where there are hard limits on the total
number of files. Larger pack sizes can also improve the backup speed for a repository
stored on a local HDD. This can be achieved by either using the ``--pack-size`` option
or defining the ``$RESTIC_PACK_SIZE`` environment variable. Restic currently defaults
to a 16 MiB pack size.
The side effect of increasing the pack size is requiring more disk space for temporary pack
files created before uploading. The space must be available in the system default temp
directory, unless overwritten by setting the ``$TMPDIR`` environment variable. In addition,
depending on the backend the memory usage can also increase by a similar amount. Restic
requires temporary space according to the pack size, multiplied by the number
of backend connections plus one. For example, if the backend uses 5 connections (the default
for most backends), with a target pack size of 64 MiB, you'll need a *minimum* of 384 MiB
of space in the temp directory. A bit of tuning may be required to strike a balance between
resource usage at the backup client and the number of pack files in the repository.
Note that larger pack files increase the chance that the temporary pack files are written
to disk. An operating system usually caches file write operations in memory and writes
them to disk after a short delay. As larger pack files take longer to upload, this
increases the chance of these files being written to disk. This can increase disk wear
for SSDs.

View File

@@ -96,7 +96,7 @@ the data directly. This can be achieved by using the `dump` command, like this:
If you have saved multiple different things into the same repo, the ``latest``
snapshot may not be the right one. For example, consider the following
snapshots in a repository:
snapshots in a repo:
.. code-block:: console

View File

@@ -212,13 +212,12 @@ The ``forget`` command accepts the following policy options:
.. note:: Specifying ``--keep-tag ''`` will match untagged snapshots only.
When ``forget`` is run with a policy, restic first loads the list of all snapshots
and groups them by their host name and paths. The grouping options can be set with
``--group-by``, e.g. using ``--group-by paths,tags`` to instead group snapshots by
paths and tags. The policy is then applied to each group of snapshots individually.
This is a safety feature to prevent accidental removal of unrelated backup sets. To
disable grouping and apply the policy to all snapshots regardless of their host,
paths and tags, use ``--group-by ''`` (that is, an empty value to ``--group-by``).
When ``forget`` is run with a policy, restic loads the list of all snapshots,
then groups these by host name and list of directories. The grouping options can
be set with ``--group-by``, to e.g. group snapshots by only paths and tags use
``--group-by paths,tags``. The policy is then applied to each group of snapshots
separately. This is a safety feature to prevent accidental removal of unrelated
backup sets.
Additionally, you can restrict the policy to only process snapshots which have a
particular hostname with the ``--host`` parameter, or tags with the ``--tag``
@@ -388,8 +387,6 @@ the specified duration: if ``forget --keep-within 7d`` is run 8 days after the
last good snapshot, then the attacker can still use that opportunity to remove
all legitimate snapshots.
.. _customize-pruning:
Customize pruning
*****************
@@ -418,9 +415,9 @@ The ``prune`` command accepts the following options:
* As an absolute size (e.g. ``200M``). If you want to minimize the space
used by your repository, pass ``0`` to this option.
* As a size relative to the total repository size (e.g. ``10%``). This means that
after prune, at most ``10%`` of the total data stored in the repository may be
unused data. If the repository after prune has a size of 500MB, then at most
* As a size relative to the total repo size (e.g. ``10%``). This means that
after prune, at most ``10%`` of the total data stored in the repo may be
unused data. If the repo after prune has as size of 500MB, then at most
50MB may be unused.
* If the string ``unlimited`` is passed, there is no limit for partly
unused files. This means that as long as some data is still used within
@@ -446,31 +443,3 @@ The ``prune`` command accepts the following options:
- ``--dry-run`` only show what ``prune`` would do.
- ``--verbose`` increased verbosity shows additional statistics for ``prune``.
Recovering from "no free space" errors
**************************************
In some cases when a repository has grown large enough to fill up all disk space or the
allocated quota, then ``prune`` might fail to free space. ``prune`` works in such a way
that a repository remains usable no matter at which point the command is interrupted.
However, this also means that ``prune`` requires some scratch space to work.
In most cases it is sufficient to instruct ``prune`` to use as little scratch space as
possible by running it as ``prune --max-repack-size 0``. Note that for restic versions
before 0.13.0 ``prune --max-repack-size 1`` must be used. Obviously, this can only work
if several snapshots have been removed using ``forget`` before. This then allows the
``prune`` command to actually remove data from the repository. If the command succeeds,
but there is still little free space, then remove a few more snapshots and run ``prune`` again.
If ``prune`` fails to complete, then ``prune --unsafe-recover-no-free-space SOME-ID``
is available as a method of last resort. It allows prune to work with little to no free
space. However, a **failed** ``prune`` run can cause the repository to become
**temporarily unusable**. Therefore, make sure that you have a stable connection to the
repository storage, before running this command. In case the command fails, it may become
necessary to manually remove all files from the `index/` folder of the repository and
run `rebuild-index` afterwards.
To prevent accidental usages of the ``--unsafe-recover-no-free-space`` option it is
necessary to first run ``prune --unsafe-recover-no-free-space SOME-ID`` and then replace
``SOME-ID`` with the requested ID.

View File

@@ -202,12 +202,11 @@ configuration of restic will be placed into environment variables. This will
include sensitive information, such as your AWS secret and repository password.
Therefore, make sure the next commands **do not** end up in your shell's
history file. Adjust the contents of the environment variables to fit your
bucket's name, region, and your user's API credentials.
bucket's name and your user's API credentials.
.. code-block:: console
$ unset HISTFILE
$ export AWS_DEFAULT_REGION="eu-west-1"
$ export RESTIC_REPOSITORY="s3:https://s3.amazonaws.com/restic-demo"
$ export AWS_ACCESS_KEY_ID="AKIAJAJSLTZCAZ4SRI5Q"
$ export AWS_SECRET_ACCESS_KEY="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"

View File

@@ -14,12 +14,18 @@
Participating
#############
**********
Debug Logs
**********
*********
Debugging
*********
Set the environment variable ``DEBUG_LOG`` to let restic write extensive debug
messages to the specified filed, e.g.:
The program can be built with debug support like this:
.. code-block:: console
$ go run build.go -tags debug
Afterwards, extensive debug messages are written to the file in
environment variable ``DEBUG_LOG``, e.g.:
.. code-block:: console
@@ -60,21 +66,6 @@ statements originating in functions that match the pattern ``*unlock*``
$ DEBUG_FUNCS=*unlock* restic check
*********
Debugging
*********
The program can be built with debug support like this:
.. code-block:: console
$ go run build.go -tags debug
This will make the ``restic debug <subcommand>`` available which can be used to
inspect internal data structures. In addition, this enables profiling support
which can help with investigation performance and memory usage issues.
************
Contributing
************

View File

@@ -2,7 +2,7 @@
__restic_debug()
{
if [[ -n ${BASH_COMP_DEBUG_FILE:-} ]]; then
if [[ -n ${BASH_COMP_DEBUG_FILE} ]]; then
echo "$*" >> "${BASH_COMP_DEBUG_FILE}"
fi
}
@@ -51,8 +51,7 @@ __restic_handle_go_custom_completion()
# Prepare the command to request completions for the program.
# Calling ${words[0]} instead of directly restic allows to handle aliases
args=("${words[@]:1}")
# Disable ActiveHelp which is not supported for bash completion v1
requestComp="RESTIC_ACTIVE_HELP=0 ${words[0]} __completeNoDesc ${args[*]}"
requestComp="${words[0]} __completeNoDesc ${args[*]}"
lastParam=${words[$((${#words[@]}-1))]}
lastChar=${lastParam:$((${#lastParam}-1)):1}
@@ -78,7 +77,7 @@ __restic_handle_go_custom_completion()
directive=0
fi
__restic_debug "${FUNCNAME[0]}: the completion directive is: ${directive}"
__restic_debug "${FUNCNAME[0]}: the completions are: ${out}"
__restic_debug "${FUNCNAME[0]}: the completions are: ${out[*]}"
if [ $((directive & shellCompDirectiveError)) -ne 0 ]; then
# Error code. No completion.
@@ -104,7 +103,7 @@ __restic_handle_go_custom_completion()
local fullFilter filter filteringCmd
# Do not use quotes around the $out variable or else newline
# characters will be kept.
for filter in ${out}; do
for filter in ${out[*]}; do
fullFilter+="$filter|"
done
@@ -113,9 +112,9 @@ __restic_handle_go_custom_completion()
$filteringCmd
elif [ $((directive & shellCompDirectiveFilterDirs)) -ne 0 ]; then
# File completion for directories only
local subdir
local subDir
# Use printf to strip any trailing newline
subdir=$(printf "%s" "${out}")
subdir=$(printf "%s" "${out[0]}")
if [ -n "$subdir" ]; then
__restic_debug "Listing directories in $subdir"
__restic_handle_subdirs_in_dir_flag "$subdir"
@@ -126,7 +125,7 @@ __restic_handle_go_custom_completion()
else
while IFS='' read -r comp; do
COMPREPLY+=("$comp")
done < <(compgen -W "${out}" -- "$cur")
done < <(compgen -W "${out[*]}" -- "$cur")
fi
}
@@ -166,19 +165,13 @@ __restic_handle_reply()
PREFIX=""
cur="${cur#*=}"
${flags_completion[${index}]}
if [ -n "${ZSH_VERSION:-}" ]; then
if [ -n "${ZSH_VERSION}" ]; then
# zsh completion needs --flag= prefix
eval "COMPREPLY=( \"\${COMPREPLY[@]/#/${flag}=}\" )"
fi
fi
fi
if [[ -z "${flag_parsing_disabled}" ]]; then
# If flag parsing is enabled, we have completed the flags and can return.
# If flag parsing is disabled, we may not know all (or any) of the flags, so we fallthrough
# to possibly call handle_go_custom_completion.
return 0;
fi
return 0;
;;
esac
@@ -217,13 +210,13 @@ __restic_handle_reply()
fi
if [[ ${#COMPREPLY[@]} -eq 0 ]]; then
if declare -F __restic_custom_func >/dev/null; then
# try command name qualified custom func
__restic_custom_func
else
# otherwise fall back to unqualified for compatibility
declare -F __custom_func >/dev/null && __custom_func
fi
if declare -F __restic_custom_func >/dev/null; then
# try command name qualified custom func
__restic_custom_func
else
# otherwise fall back to unqualified for compatibility
declare -F __custom_func >/dev/null && __custom_func
fi
fi
# available in bash-completion >= 2, not always present on macOS
@@ -257,7 +250,7 @@ __restic_handle_flag()
# if a command required a flag, and we found it, unset must_have_one_flag()
local flagname=${words[c]}
local flagvalue=""
local flagvalue
# if the word contained an =
if [[ ${words[c]} == *"="* ]]; then
flagvalue=${flagname#*=} # take in as flagvalue after the =
@@ -276,7 +269,7 @@ __restic_handle_flag()
# keep flag value with flagname as flaghash
# flaghash variable is an associative array which is only supported in bash > 3.
if [[ -z "${BASH_VERSION:-}" || "${BASH_VERSINFO[0]:-}" -gt 3 ]]; then
if [[ -z "${BASH_VERSION}" || "${BASH_VERSINFO[0]}" -gt 3 ]]; then
if [ -n "${flagvalue}" ] ; then
flaghash[${flagname}]=${flagvalue}
elif [ -n "${words[ $((c+1)) ]}" ] ; then
@@ -288,7 +281,7 @@ __restic_handle_flag()
# skip the argument to a two word flag
if [[ ${words[c]} != *"="* ]] && __restic_contains_word "${words[c]}" "${two_word_flags[@]}"; then
__restic_debug "${FUNCNAME[0]}: found a flag ${words[c]}, skip the next argument"
__restic_debug "${FUNCNAME[0]}: found a flag ${words[c]}, skip the next argument"
c=$((c+1))
# if we are looking for a flags value, don't show commands
if [[ $c -eq $cword ]]; then
@@ -348,7 +341,7 @@ __restic_handle_word()
__restic_handle_command
elif __restic_contains_word "${words[c]}" "${command_aliases[@]}"; then
# aliashash variable is an associative array which is only supported in bash > 3.
if [[ -z "${BASH_VERSION:-}" || "${BASH_VERSINFO[0]:-}" -gt 3 ]]; then
if [[ -z "${BASH_VERSION}" || "${BASH_VERSINFO[0]}" -gt 3 ]]; then
words[c]=${aliashash[${words[c]}]}
__restic_handle_command
else
@@ -465,8 +458,6 @@ _restic_backup()
flags+=("--cache-dir=")
two_word_flags+=("--cache-dir")
flags+=("--cleanup-cache")
flags+=("--compression=")
two_word_flags+=("--compression")
flags+=("--insecure-tls")
flags+=("--json")
flags+=("--key-hint=")
@@ -480,8 +471,6 @@ _restic_backup()
flags+=("--option=")
two_word_flags+=("--option")
two_word_flags+=("-o")
flags+=("--pack-size=")
two_word_flags+=("--pack-size")
flags+=("--password-command=")
two_word_flags+=("--password-command")
flags+=("--password-file=")
@@ -535,8 +524,6 @@ _restic_cache()
flags+=("--cache-dir=")
two_word_flags+=("--cache-dir")
flags+=("--cleanup-cache")
flags+=("--compression=")
two_word_flags+=("--compression")
flags+=("--insecure-tls")
flags+=("--json")
flags+=("--key-hint=")
@@ -550,8 +537,6 @@ _restic_cache()
flags+=("--option=")
two_word_flags+=("--option")
two_word_flags+=("-o")
flags+=("--pack-size=")
two_word_flags+=("--pack-size")
flags+=("--password-command=")
two_word_flags+=("--password-command")
flags+=("--password-file=")
@@ -597,8 +582,6 @@ _restic_cat()
flags+=("--cache-dir=")
two_word_flags+=("--cache-dir")
flags+=("--cleanup-cache")
flags+=("--compression=")
two_word_flags+=("--compression")
flags+=("--insecure-tls")
flags+=("--json")
flags+=("--key-hint=")
@@ -612,8 +595,6 @@ _restic_cat()
flags+=("--option=")
two_word_flags+=("--option")
two_word_flags+=("-o")
flags+=("--pack-size=")
two_word_flags+=("--pack-size")
flags+=("--password-command=")
two_word_flags+=("--password-command")
flags+=("--password-file=")
@@ -650,6 +631,8 @@ _restic_check()
flags_with_completion=()
flags_completion=()
flags+=("--check-unused")
local_nonpersistent_flags+=("--check-unused")
flags+=("--help")
flags+=("-h")
local_nonpersistent_flags+=("--help")
@@ -667,8 +650,6 @@ _restic_check()
flags+=("--cache-dir=")
two_word_flags+=("--cache-dir")
flags+=("--cleanup-cache")
flags+=("--compression=")
two_word_flags+=("--compression")
flags+=("--insecure-tls")
flags+=("--json")
flags+=("--key-hint=")
@@ -682,8 +663,6 @@ _restic_check()
flags+=("--option=")
two_word_flags+=("--option")
two_word_flags+=("-o")
flags+=("--pack-size=")
two_word_flags+=("--pack-size")
flags+=("--password-command=")
two_word_flags+=("--password-command")
flags+=("--password-file=")
@@ -720,26 +699,6 @@ _restic_copy()
flags_with_completion=()
flags_completion=()
flags+=("--from-key-hint=")
two_word_flags+=("--from-key-hint")
local_nonpersistent_flags+=("--from-key-hint")
local_nonpersistent_flags+=("--from-key-hint=")
flags+=("--from-password-command=")
two_word_flags+=("--from-password-command")
local_nonpersistent_flags+=("--from-password-command")
local_nonpersistent_flags+=("--from-password-command=")
flags+=("--from-password-file=")
two_word_flags+=("--from-password-file")
local_nonpersistent_flags+=("--from-password-file")
local_nonpersistent_flags+=("--from-password-file=")
flags+=("--from-repo=")
two_word_flags+=("--from-repo")
local_nonpersistent_flags+=("--from-repo")
local_nonpersistent_flags+=("--from-repo=")
flags+=("--from-repository-file=")
two_word_flags+=("--from-repository-file")
local_nonpersistent_flags+=("--from-repository-file")
local_nonpersistent_flags+=("--from-repository-file=")
flags+=("--help")
flags+=("-h")
local_nonpersistent_flags+=("--help")
@@ -750,10 +709,30 @@ _restic_copy()
local_nonpersistent_flags+=("--host")
local_nonpersistent_flags+=("--host=")
local_nonpersistent_flags+=("-H")
flags+=("--key-hint2=")
two_word_flags+=("--key-hint2")
local_nonpersistent_flags+=("--key-hint2")
local_nonpersistent_flags+=("--key-hint2=")
flags+=("--password-command2=")
two_word_flags+=("--password-command2")
local_nonpersistent_flags+=("--password-command2")
local_nonpersistent_flags+=("--password-command2=")
flags+=("--password-file2=")
two_word_flags+=("--password-file2")
local_nonpersistent_flags+=("--password-file2")
local_nonpersistent_flags+=("--password-file2=")
flags+=("--path=")
two_word_flags+=("--path")
local_nonpersistent_flags+=("--path")
local_nonpersistent_flags+=("--path=")
flags+=("--repo2=")
two_word_flags+=("--repo2")
local_nonpersistent_flags+=("--repo2")
local_nonpersistent_flags+=("--repo2=")
flags+=("--repository-file2=")
two_word_flags+=("--repository-file2")
local_nonpersistent_flags+=("--repository-file2")
local_nonpersistent_flags+=("--repository-file2=")
flags+=("--tag=")
two_word_flags+=("--tag")
local_nonpersistent_flags+=("--tag")
@@ -763,8 +742,6 @@ _restic_copy()
flags+=("--cache-dir=")
two_word_flags+=("--cache-dir")
flags+=("--cleanup-cache")
flags+=("--compression=")
two_word_flags+=("--compression")
flags+=("--insecure-tls")
flags+=("--json")
flags+=("--key-hint=")
@@ -778,8 +755,6 @@ _restic_copy()
flags+=("--option=")
two_word_flags+=("--option")
two_word_flags+=("-o")
flags+=("--pack-size=")
two_word_flags+=("--pack-size")
flags+=("--password-command=")
two_word_flags+=("--password-command")
flags+=("--password-file=")
@@ -827,8 +802,6 @@ _restic_diff()
flags+=("--cache-dir=")
two_word_flags+=("--cache-dir")
flags+=("--cleanup-cache")
flags+=("--compression=")
two_word_flags+=("--compression")
flags+=("--insecure-tls")
flags+=("--json")
flags+=("--key-hint=")
@@ -842,8 +815,6 @@ _restic_diff()
flags+=("--option=")
two_word_flags+=("--option")
two_word_flags+=("-o")
flags+=("--pack-size=")
two_word_flags+=("--pack-size")
flags+=("--password-command=")
two_word_flags+=("--password-command")
flags+=("--password-file=")
@@ -909,8 +880,6 @@ _restic_dump()
flags+=("--cache-dir=")
two_word_flags+=("--cache-dir")
flags+=("--cleanup-cache")
flags+=("--compression=")
two_word_flags+=("--compression")
flags+=("--insecure-tls")
flags+=("--json")
flags+=("--key-hint=")
@@ -924,8 +893,6 @@ _restic_dump()
flags+=("--option=")
two_word_flags+=("--option")
two_word_flags+=("-o")
flags+=("--pack-size=")
two_word_flags+=("--pack-size")
flags+=("--password-command=")
two_word_flags+=("--password-command")
flags+=("--password-file=")
@@ -1019,8 +986,6 @@ _restic_find()
flags+=("--cache-dir=")
two_word_flags+=("--cache-dir")
flags+=("--cleanup-cache")
flags+=("--compression=")
two_word_flags+=("--compression")
flags+=("--insecure-tls")
flags+=("--json")
flags+=("--key-hint=")
@@ -1034,8 +999,6 @@ _restic_find()
flags+=("--option=")
two_word_flags+=("--option")
two_word_flags+=("-o")
flags+=("--pack-size=")
two_word_flags+=("--pack-size")
flags+=("--password-command=")
two_word_flags+=("--password-command")
flags+=("--password-file=")
@@ -1174,10 +1137,6 @@ _restic_forget()
local_nonpersistent_flags+=("--max-repack-size=")
flags+=("--repack-cacheable-only")
local_nonpersistent_flags+=("--repack-cacheable-only")
flags+=("--repack-small")
local_nonpersistent_flags+=("--repack-small")
flags+=("--repack-uncompressed")
local_nonpersistent_flags+=("--repack-uncompressed")
flags+=("--help")
flags+=("-h")
local_nonpersistent_flags+=("--help")
@@ -1187,8 +1146,6 @@ _restic_forget()
flags+=("--cache-dir=")
two_word_flags+=("--cache-dir")
flags+=("--cleanup-cache")
flags+=("--compression=")
two_word_flags+=("--compression")
flags+=("--insecure-tls")
flags+=("--json")
flags+=("--key-hint=")
@@ -1202,8 +1159,6 @@ _restic_forget()
flags+=("--option=")
two_word_flags+=("--option")
two_word_flags+=("-o")
flags+=("--pack-size=")
two_word_flags+=("--pack-size")
flags+=("--password-command=")
two_word_flags+=("--password-command")
flags+=("--password-file=")
@@ -1265,8 +1220,6 @@ _restic_generate()
flags+=("--cache-dir=")
two_word_flags+=("--cache-dir")
flags+=("--cleanup-cache")
flags+=("--compression=")
two_word_flags+=("--compression")
flags+=("--insecure-tls")
flags+=("--json")
flags+=("--key-hint=")
@@ -1280,8 +1233,6 @@ _restic_generate()
flags+=("--option=")
two_word_flags+=("--option")
two_word_flags+=("-o")
flags+=("--pack-size=")
two_word_flags+=("--pack-size")
flags+=("--password-command=")
two_word_flags+=("--password-command")
flags+=("--password-file=")
@@ -1323,8 +1274,6 @@ _restic_help()
flags+=("--cache-dir=")
two_word_flags+=("--cache-dir")
flags+=("--cleanup-cache")
flags+=("--compression=")
two_word_flags+=("--compression")
flags+=("--insecure-tls")
flags+=("--json")
flags+=("--key-hint=")
@@ -1338,8 +1287,6 @@ _restic_help()
flags+=("--option=")
two_word_flags+=("--option")
two_word_flags+=("-o")
flags+=("--pack-size=")
two_word_flags+=("--pack-size")
flags+=("--password-command=")
two_word_flags+=("--password-command")
flags+=("--password-file=")
@@ -1379,41 +1326,35 @@ _restic_init()
flags+=("--copy-chunker-params")
local_nonpersistent_flags+=("--copy-chunker-params")
flags+=("--from-key-hint=")
two_word_flags+=("--from-key-hint")
local_nonpersistent_flags+=("--from-key-hint")
local_nonpersistent_flags+=("--from-key-hint=")
flags+=("--from-password-command=")
two_word_flags+=("--from-password-command")
local_nonpersistent_flags+=("--from-password-command")
local_nonpersistent_flags+=("--from-password-command=")
flags+=("--from-password-file=")
two_word_flags+=("--from-password-file")
local_nonpersistent_flags+=("--from-password-file")
local_nonpersistent_flags+=("--from-password-file=")
flags+=("--from-repo=")
two_word_flags+=("--from-repo")
local_nonpersistent_flags+=("--from-repo")
local_nonpersistent_flags+=("--from-repo=")
flags+=("--from-repository-file=")
two_word_flags+=("--from-repository-file")
local_nonpersistent_flags+=("--from-repository-file")
local_nonpersistent_flags+=("--from-repository-file=")
flags+=("--help")
flags+=("-h")
local_nonpersistent_flags+=("--help")
local_nonpersistent_flags+=("-h")
flags+=("--repository-version=")
two_word_flags+=("--repository-version")
local_nonpersistent_flags+=("--repository-version")
local_nonpersistent_flags+=("--repository-version=")
flags+=("--key-hint2=")
two_word_flags+=("--key-hint2")
local_nonpersistent_flags+=("--key-hint2")
local_nonpersistent_flags+=("--key-hint2=")
flags+=("--password-command2=")
two_word_flags+=("--password-command2")
local_nonpersistent_flags+=("--password-command2")
local_nonpersistent_flags+=("--password-command2=")
flags+=("--password-file2=")
two_word_flags+=("--password-file2")
local_nonpersistent_flags+=("--password-file2")
local_nonpersistent_flags+=("--password-file2=")
flags+=("--repo2=")
two_word_flags+=("--repo2")
local_nonpersistent_flags+=("--repo2")
local_nonpersistent_flags+=("--repo2=")
flags+=("--repository-file2=")
two_word_flags+=("--repository-file2")
local_nonpersistent_flags+=("--repository-file2")
local_nonpersistent_flags+=("--repository-file2=")
flags+=("--cacert=")
two_word_flags+=("--cacert")
flags+=("--cache-dir=")
two_word_flags+=("--cache-dir")
flags+=("--cleanup-cache")
flags+=("--compression=")
two_word_flags+=("--compression")
flags+=("--insecure-tls")
flags+=("--json")
flags+=("--key-hint=")
@@ -1427,8 +1368,6 @@ _restic_init()
flags+=("--option=")
two_word_flags+=("--option")
two_word_flags+=("-o")
flags+=("--pack-size=")
two_word_flags+=("--pack-size")
flags+=("--password-command=")
two_word_flags+=("--password-command")
flags+=("--password-file=")
@@ -1486,8 +1425,6 @@ _restic_key()
flags+=("--cache-dir=")
two_word_flags+=("--cache-dir")
flags+=("--cleanup-cache")
flags+=("--compression=")
two_word_flags+=("--compression")
flags+=("--insecure-tls")
flags+=("--json")
flags+=("--key-hint=")
@@ -1501,8 +1438,6 @@ _restic_key()
flags+=("--option=")
two_word_flags+=("--option")
two_word_flags+=("-o")
flags+=("--pack-size=")
two_word_flags+=("--pack-size")
flags+=("--password-command=")
two_word_flags+=("--password-command")
flags+=("--password-file=")
@@ -1548,8 +1483,6 @@ _restic_list()
flags+=("--cache-dir=")
two_word_flags+=("--cache-dir")
flags+=("--cleanup-cache")
flags+=("--compression=")
two_word_flags+=("--compression")
flags+=("--insecure-tls")
flags+=("--json")
flags+=("--key-hint=")
@@ -1563,8 +1496,6 @@ _restic_list()
flags+=("--option=")
two_word_flags+=("--option")
two_word_flags+=("-o")
flags+=("--pack-size=")
two_word_flags+=("--pack-size")
flags+=("--password-command=")
two_word_flags+=("--password-command")
flags+=("--password-file=")
@@ -1630,8 +1561,6 @@ _restic_ls()
flags+=("--cache-dir=")
two_word_flags+=("--cache-dir")
flags+=("--cleanup-cache")
flags+=("--compression=")
two_word_flags+=("--compression")
flags+=("--insecure-tls")
flags+=("--json")
flags+=("--key-hint=")
@@ -1645,8 +1574,6 @@ _restic_ls()
flags+=("--option=")
two_word_flags+=("--option")
two_word_flags+=("-o")
flags+=("--pack-size=")
two_word_flags+=("--pack-size")
flags+=("--password-command=")
two_word_flags+=("--password-command")
flags+=("--password-file=")
@@ -1696,8 +1623,6 @@ _restic_migrate()
flags+=("--cache-dir=")
two_word_flags+=("--cache-dir")
flags+=("--cleanup-cache")
flags+=("--compression=")
two_word_flags+=("--compression")
flags+=("--insecure-tls")
flags+=("--json")
flags+=("--key-hint=")
@@ -1711,8 +1636,6 @@ _restic_migrate()
flags+=("--option=")
two_word_flags+=("--option")
two_word_flags+=("-o")
flags+=("--pack-size=")
two_word_flags+=("--pack-size")
flags+=("--password-command=")
two_word_flags+=("--password-command")
flags+=("--password-file=")
@@ -1769,25 +1692,19 @@ _restic_mount()
two_word_flags+=("--path")
local_nonpersistent_flags+=("--path")
local_nonpersistent_flags+=("--path=")
flags+=("--path-template=")
two_word_flags+=("--path-template")
local_nonpersistent_flags+=("--path-template")
local_nonpersistent_flags+=("--path-template=")
flags+=("--snapshot-template=")
two_word_flags+=("--snapshot-template")
local_nonpersistent_flags+=("--snapshot-template")
local_nonpersistent_flags+=("--snapshot-template=")
flags+=("--tag=")
two_word_flags+=("--tag")
local_nonpersistent_flags+=("--tag")
local_nonpersistent_flags+=("--tag=")
flags+=("--time-template=")
two_word_flags+=("--time-template")
local_nonpersistent_flags+=("--time-template")
local_nonpersistent_flags+=("--time-template=")
flags+=("--cacert=")
two_word_flags+=("--cacert")
flags+=("--cache-dir=")
two_word_flags+=("--cache-dir")
flags+=("--cleanup-cache")
flags+=("--compression=")
two_word_flags+=("--compression")
flags+=("--insecure-tls")
flags+=("--json")
flags+=("--key-hint=")
@@ -1801,8 +1718,6 @@ _restic_mount()
flags+=("--option=")
two_word_flags+=("--option")
two_word_flags+=("-o")
flags+=("--pack-size=")
two_word_flags+=("--pack-size")
flags+=("--password-command=")
two_word_flags+=("--password-command")
flags+=("--password-file=")
@@ -1857,21 +1772,11 @@ _restic_prune()
local_nonpersistent_flags+=("--max-unused=")
flags+=("--repack-cacheable-only")
local_nonpersistent_flags+=("--repack-cacheable-only")
flags+=("--repack-small")
local_nonpersistent_flags+=("--repack-small")
flags+=("--repack-uncompressed")
local_nonpersistent_flags+=("--repack-uncompressed")
flags+=("--unsafe-recover-no-free-space=")
two_word_flags+=("--unsafe-recover-no-free-space")
local_nonpersistent_flags+=("--unsafe-recover-no-free-space")
local_nonpersistent_flags+=("--unsafe-recover-no-free-space=")
flags+=("--cacert=")
two_word_flags+=("--cacert")
flags+=("--cache-dir=")
two_word_flags+=("--cache-dir")
flags+=("--cleanup-cache")
flags+=("--compression=")
two_word_flags+=("--compression")
flags+=("--insecure-tls")
flags+=("--json")
flags+=("--key-hint=")
@@ -1885,8 +1790,6 @@ _restic_prune()
flags+=("--option=")
two_word_flags+=("--option")
two_word_flags+=("-o")
flags+=("--pack-size=")
two_word_flags+=("--pack-size")
flags+=("--password-command=")
two_word_flags+=("--password-command")
flags+=("--password-file=")
@@ -1934,8 +1837,6 @@ _restic_rebuild-index()
flags+=("--cache-dir=")
two_word_flags+=("--cache-dir")
flags+=("--cleanup-cache")
flags+=("--compression=")
two_word_flags+=("--compression")
flags+=("--insecure-tls")
flags+=("--json")
flags+=("--key-hint=")
@@ -1949,8 +1850,6 @@ _restic_rebuild-index()
flags+=("--option=")
two_word_flags+=("--option")
two_word_flags+=("-o")
flags+=("--pack-size=")
two_word_flags+=("--pack-size")
flags+=("--password-command=")
two_word_flags+=("--password-command")
flags+=("--password-file=")
@@ -1996,8 +1895,6 @@ _restic_recover()
flags+=("--cache-dir=")
two_word_flags+=("--cache-dir")
flags+=("--cleanup-cache")
flags+=("--compression=")
two_word_flags+=("--compression")
flags+=("--insecure-tls")
flags+=("--json")
flags+=("--key-hint=")
@@ -2011,8 +1908,6 @@ _restic_recover()
flags+=("--option=")
two_word_flags+=("--option")
two_word_flags+=("-o")
flags+=("--pack-size=")
two_word_flags+=("--pack-size")
flags+=("--password-command=")
two_word_flags+=("--password-command")
flags+=("--password-file=")
@@ -2100,8 +1995,6 @@ _restic_restore()
flags+=("--cache-dir=")
two_word_flags+=("--cache-dir")
flags+=("--cleanup-cache")
flags+=("--compression=")
two_word_flags+=("--compression")
flags+=("--insecure-tls")
flags+=("--json")
flags+=("--key-hint=")
@@ -2115,8 +2008,6 @@ _restic_restore()
flags+=("--option=")
two_word_flags+=("--option")
two_word_flags+=("-o")
flags+=("--pack-size=")
two_word_flags+=("--pack-size")
flags+=("--password-command=")
two_word_flags+=("--password-command")
flags+=("--password-file=")
@@ -2166,8 +2057,6 @@ _restic_self-update()
flags+=("--cache-dir=")
two_word_flags+=("--cache-dir")
flags+=("--cleanup-cache")
flags+=("--compression=")
two_word_flags+=("--compression")
flags+=("--insecure-tls")
flags+=("--json")
flags+=("--key-hint=")
@@ -2181,8 +2070,6 @@ _restic_self-update()
flags+=("--option=")
two_word_flags+=("--option")
two_word_flags+=("-o")
flags+=("--pack-size=")
two_word_flags+=("--pack-size")
flags+=("--password-command=")
two_word_flags+=("--password-command")
flags+=("--password-file=")
@@ -2256,8 +2143,6 @@ _restic_snapshots()
flags+=("--cache-dir=")
two_word_flags+=("--cache-dir")
flags+=("--cleanup-cache")
flags+=("--compression=")
two_word_flags+=("--compression")
flags+=("--insecure-tls")
flags+=("--json")
flags+=("--key-hint=")
@@ -2271,8 +2156,6 @@ _restic_snapshots()
flags+=("--option=")
two_word_flags+=("--option")
two_word_flags+=("-o")
flags+=("--pack-size=")
two_word_flags+=("--pack-size")
flags+=("--password-command=")
two_word_flags+=("--password-command")
flags+=("--password-file=")
@@ -2336,8 +2219,6 @@ _restic_stats()
flags+=("--cache-dir=")
two_word_flags+=("--cache-dir")
flags+=("--cleanup-cache")
flags+=("--compression=")
two_word_flags+=("--compression")
flags+=("--insecure-tls")
flags+=("--json")
flags+=("--key-hint=")
@@ -2351,8 +2232,6 @@ _restic_stats()
flags+=("--option=")
two_word_flags+=("--option")
two_word_flags+=("-o")
flags+=("--pack-size=")
two_word_flags+=("--pack-size")
flags+=("--password-command=")
two_word_flags+=("--password-command")
flags+=("--password-file=")
@@ -2424,8 +2303,6 @@ _restic_tag()
flags+=("--cache-dir=")
two_word_flags+=("--cache-dir")
flags+=("--cleanup-cache")
flags+=("--compression=")
two_word_flags+=("--compression")
flags+=("--insecure-tls")
flags+=("--json")
flags+=("--key-hint=")
@@ -2439,8 +2316,6 @@ _restic_tag()
flags+=("--option=")
two_word_flags+=("--option")
two_word_flags+=("-o")
flags+=("--pack-size=")
two_word_flags+=("--pack-size")
flags+=("--password-command=")
two_word_flags+=("--password-command")
flags+=("--password-file=")
@@ -2488,8 +2363,6 @@ _restic_unlock()
flags+=("--cache-dir=")
two_word_flags+=("--cache-dir")
flags+=("--cleanup-cache")
flags+=("--compression=")
two_word_flags+=("--compression")
flags+=("--insecure-tls")
flags+=("--json")
flags+=("--key-hint=")
@@ -2503,8 +2376,6 @@ _restic_unlock()
flags+=("--option=")
two_word_flags+=("--option")
two_word_flags+=("-o")
flags+=("--pack-size=")
two_word_flags+=("--pack-size")
flags+=("--password-command=")
two_word_flags+=("--password-command")
flags+=("--password-file=")
@@ -2550,8 +2421,6 @@ _restic_version()
flags+=("--cache-dir=")
two_word_flags+=("--cache-dir")
flags+=("--cleanup-cache")
flags+=("--compression=")
two_word_flags+=("--compression")
flags+=("--insecure-tls")
flags+=("--json")
flags+=("--key-hint=")
@@ -2565,8 +2434,6 @@ _restic_version()
flags+=("--option=")
two_word_flags+=("--option")
two_word_flags+=("-o")
flags+=("--pack-size=")
two_word_flags+=("--pack-size")
flags+=("--password-command=")
two_word_flags+=("--password-command")
flags+=("--password-file=")
@@ -2635,8 +2502,6 @@ _restic_root_command()
flags+=("--cache-dir=")
two_word_flags+=("--cache-dir")
flags+=("--cleanup-cache")
flags+=("--compression=")
two_word_flags+=("--compression")
flags+=("--help")
flags+=("-h")
local_nonpersistent_flags+=("--help")
@@ -2654,8 +2519,6 @@ _restic_root_command()
flags+=("--option=")
two_word_flags+=("--option")
two_word_flags+=("-o")
flags+=("--pack-size=")
two_word_flags+=("--pack-size")
flags+=("--password-command=")
two_word_flags+=("--password-command")
flags+=("--password-file=")
@@ -2690,7 +2553,6 @@ __start_restic()
fi
local c=0
local flag_parsing_disabled=
local flags=()
local two_word_flags=()
local local_nonpersistent_flags=()
@@ -2700,8 +2562,8 @@ __start_restic()
local command_aliases=()
local must_have_one_flag=()
local must_have_one_noun=()
local has_completion_function=""
local last_command=""
local has_completion_function
local last_command
local nouns=()
local noun_aliases=()

View File

@@ -7,7 +7,7 @@ The location of the cache directory depends on the operating system and the
environment; see :ref:`caching`.
Each repository has its own cache sub-directory, consisting of the repository ID
which is chosen at ``init``. All cache directories for different repositories are
which is chosen at ``init``. All cache directories for different repos are
independent of each other.
Snapshots, Data and Indexes
@@ -19,8 +19,8 @@ Snapshot, Data and Index files are cached in the sub-directories ``snapshots``,
Expiry
======
Whenever a cache directory for a repository is used, that directory's modification
Whenever a cache directory for a repo is used, that directory's modification
timestamp is updated to the current time. By looking at the modification
timestamps of the repository cache directories it is easy to decide which directories
timestamps of the repo cache directories it is easy to decide which directories
are old and haven't been used in a long time. Those are probably stale and can
be removed.

View File

@@ -30,10 +30,9 @@ All data is stored in a restic repository. A repository is able to store
data of several different types, which can later be requested based on
an ID. This so-called "storage ID" is the SHA-256 hash of the content of
a file. All files in a repository are only written once and never
modified afterwards. Writing should occur atomically to prevent concurrent
operations from reading incomplete files. This allows accessing and even
writing to the repository with multiple clients in parallel. Only the ``prune``
operation removes data from the repository.
modified afterwards. This allows accessing and even writing to the
repository with multiple clients in parallel. Only the ``prune`` operation
removes data from the repository.
Repositories consist of several directories and a top-level file called
``config``. For all other files stored in the repository, the name for
@@ -62,30 +61,28 @@ like the following:
.. code:: json
{
"version": 2,
"version": 1,
"id": "5956a3f67a6230d4a92cefb29529f10196c7d92582ec305fd71ff6d331d6271b",
"chunker_polynomial": "25b468838dcb75"
}
After decryption, restic first checks that the version field contains a
version number that it understands, otherwise it aborts. At the moment, the
version is expected to be 1 or 2. The list of changes in the repository
format is contained in the section "Changes" below.
The field ``id`` holds a unique ID which consists of 32 random bytes, encoded
in hexadecimal. This uniquely identifies the repository, regardless if it is
accessed via a remote storage backend or locally. The field
``chunker_polynomial`` contains a parameter that is used for splitting large
files into smaller chunks (see below).
version number that it understands, otherwise it aborts. At the moment,
the version is expected to be 1. The field ``id`` holds a unique ID
which consists of 32 random bytes, encoded in hexadecimal. This uniquely
identifies the repository, regardless if it is accessed via SFTP or
locally. The field ``chunker_polynomial`` contains a parameter that is
used for splitting large files into smaller chunks (see below).
Repository Layout
-----------------
The ``local`` and ``sftp`` backends are implemented using files and
directories stored in a file system. The directory layout is the same
for both backend types and is also used for all other remote backends.
for both backend types.
The basic layout of a repository is shown here:
The basic layout of a repository stored in a ``local`` or ``sftp``
backend is shown here:
::
@@ -111,7 +108,8 @@ The basic layout of a repository is shown here:
│ └── 22a5af1bdc6e616f8a29579458c49627e01b32210d09adb288d1ecda7c5711ec
└── tmp
A local repository can be initialized with the ``restic init`` command, e.g.:
A local repository can be initialized with the ``restic init`` command,
e.g.:
.. code-block:: console
@@ -187,75 +185,40 @@ After decryption, a Pack's header consists of the following elements:
::
Type_Blob1 || Data_Blob1 ||
Type_Blob1 || Length(EncryptedBlob1) || Hash(Plaintext_Blob1) ||
[...]
Type_BlobN || Data_BlobN ||
The Blob type field is a single byte. What follows it depends on the type. The
following Blob types are defined:
+-----------+----------------------+-------------------------------------------------------------------------------+
| Type | Meaning | Data |
+===========+======================+===============================================================================+
| 0b00 | data blob | ``Length(encrypted_blob) || Hash(plaintext_blob)`` |
+-----------+----------------------+-------------------------------------------------------------------------------+
| 0b01 | tree blob | ``Length(encrypted_blob) || Hash(plaintext_blob)`` |
+-----------+----------------------+-------------------------------------------------------------------------------+
| 0b10 | compressed data blob | ``Length(encrypted_blob) || Length(plaintext_blob) || Hash(plaintext_blob)`` |
+-----------+----------------------+-------------------------------------------------------------------------------+
| 0b11 | compressed tree blob | ``Length(encrypted_blob) || Length(plaintext_blob) || Hash(plaintext_blob)`` |
+-----------+----------------------+-------------------------------------------------------------------------------+
Type_BlobN || Length(EncryptedBlobN) || Hash(Plaintext_Blobn) ||
This is enough to calculate the offsets for all the Blobs in the Pack.
The length fields are encoded as four byte integers in little-endian
format. In the Data column, ``Length(plaintext_blob)`` means the length
of the decrypted and uncompressed data a blob consists of.
Length is the length of a Blob as a four byte integer in little-endian
format. The type field is a one byte field and labels the content of a
blob according to the following table:
All other types are invalid, more types may be added in the future. The
compressed types are only valid for repository format version 2. Data and
tree blobs may be compressed with the zstandard compression algorithm.
+--------+-----------+
| Type | Meaning |
+========+===========+
| 0 | data |
+--------+-----------+
| 1 | tree |
+--------+-----------+
In repository format version 1, data and tree blobs should be stored in
separate pack files. In version 2, they must be stored in separate files.
Compressed and non-compress blobs of the same type may be mixed in a pack
file.
All other types are invalid, more types may be added in the future.
For reconstructing the index or parsing a pack without an index, first
the last four bytes must be read in order to find the length of the
header. Afterwards, the header can be read and parsed, which yields all
plaintext hashes, types, offsets and lengths of all included blobs.
Unpacked Data Format
====================
Individual files for the index, locks or snapshots are encrypted
and authenticated like Data and Tree Blobs, so the outer structure is
``IV || Ciphertext || MAC`` again. In repository format version 1 the
plaintext always consists of a JSON document which must either be an
object or an array.
Repository format version 2 adds support for compression. The plaintext
now starts with a header to indicate the encoding version to distinguish
it from plain JSON and to allow for further evolution of the storage format:
``encoding_version || data``
The ``encoding_version`` field is encoded as one byte.
For backwards compatibility the encoding versions '[' (0x5b) and '{' (0x7b)
are used to mark that the whole plaintext (including the encoding version
byte) should treated as JSON document.
For new data the encoding version is currently always ``2``. For that
version ``data`` contains a JSON document compressed using the zstandard
compression algorithm.
Indexing
========
Index files contain information about Data and Tree Blobs and the Packs
they are contained in and store this information in the repository. When
the local cached index is not accessible any more, the index files can
be downloaded and used to reconstruct the index. The file encoding is
described in the "Unpacked Data Format" section. The plaintext consists
of a JSON document like the following:
be downloaded and used to reconstruct the index. The files are encrypted
and authenticated like Data and Tree Blobs, so the outer structure is
``IV || Ciphertext || MAC`` again. The plaintext consists of a JSON
document like the following:
.. code:: json
@@ -271,22 +234,18 @@ of a JSON document like the following:
"id": "3ec79977ef0cf5de7b08cd12b874cd0f62bbaf7f07f3497a5b1bbcc8cb39b1ce",
"type": "data",
"offset": 0,
"length": 38,
// no 'uncompressed_length' as blob is not compressed
},
{
"length": 25
},{
"id": "9ccb846e60d90d4eb915848add7aa7ea1e4bbabfc60e573db9f7bfb2789afbae",
"type": "tree",
"offset": 38,
"length": 112,
"uncompressed_length": 511,
"length": 100
},
{
"id": "d3dc577b4ffd38cc4b32122cabf8655a0223ed22edfd93b353dc0c3f2b0fdf66",
"type": "data",
"offset": 150,
"length": 123,
"uncompressed_length": 234,
"length": 123
}
]
}, [...]
@@ -295,11 +254,7 @@ of a JSON document like the following:
This JSON document lists Packs and the blobs contained therein. In this
example, the Pack ``73d04e61`` contains two data Blobs and one Tree
blob, the plaintext hashes are listed afterwards. The ``length`` field
corresponds to ``Length(encrypted_blob)`` in the pack file header.
Field ``uncompressed_length`` is only present for compressed blobs and
therefore is never present in version 1. It is set to the value of
``Length(blob)``.
blob, the plaintext hashes are listed afterwards.
The field ``supersedes`` lists the storage IDs of index files that have
been replaced with the current index file. This happens when index files
@@ -316,7 +271,7 @@ Keys, Encryption and MAC
All data stored by restic in the repository is encrypted with AES-256 in
counter mode and authenticated using Poly1305-AES. For encrypting new
data first 16 bytes are read from a cryptographically secure
pseudo-random number generator as a random nonce. This is used both as
pseudorandom number generator as a random nonce. This is used both as
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
@@ -394,9 +349,8 @@ Snapshots
A snapshot represents a directory with all files and sub-directories at
a given point in time. For each backup that is made, a new snapshot is
created. A snapshot is a JSON document that is stored in a file below
the directory ``snapshots`` in the repository. It uses the file encoding
described in the "Unpacked Data Format" section. The filename
created. A snapshot is a JSON document that is stored in an encrypted
file below the directory ``snapshots`` in the repository. The filename
is the storage ID. This string is unique and used within restic to
uniquely identify a snapshot.
@@ -457,7 +411,7 @@ Blobs of data. The SHA-256 hashes of all Blobs are saved in an ordered
list which then represents the content of the file.
In order to relate these plaintext hashes to the actual location within
a Pack file, an index is used. If the index is not available, the
a Pack file , an index is used. If the index is not available, the
header of all data Blobs can be read.
Trees and Data
@@ -562,8 +516,8 @@ time there must not be any other locks (exclusive and non-exclusive).
There may be multiple non-exclusive locks in parallel.
A lock is a file in the subdir ``locks`` whose filename is the storage
ID of the contents. It is stored in the file encoding described in the
"Unpacked Data Format" section and contains the following JSON structure:
ID of the contents. It is encrypted and authenticated the same way as
other files in the repository and contains the following JSON structure:
.. code:: json
@@ -590,57 +544,6 @@ detected, restic creates a new lock, waits, and checks if other locks
appeared in the repository. Depending on the type of the other locks and
the lock to be created, restic either continues or fails.
Read and Write Ordering
=======================
The repository format allows writing (e.g. backup) and reading (e.g. restore)
to happen concurrently. As the data for each snapshot in a repository spans
multiple files (snapshot, index and packs), it is necessary to follow certain
rules regarding the order in which files are read and written. These ordering
rules also guarantee that repository modifications always maintain a correct
repository even if the client or the storage backend crashes for example due
to a power cut or the (network) connection between both is interrupted.
The correct order to access data in a repository is derived from the following
set of invariants that must be maintained at **any time** in a correct
repository. *Must* in the following is a strict requirement and will lead to
data loss if not followed. *Should* will require steps to fix a repository
(e.g. rebuilding the index) if not followed, but should not cause data loss.
*existing* means that the referenced data is **durably** stored in the repository.
- A snapshot *must* only reference an existing tree blob.
- A reachable tree blob *must* only reference tree and data blobs that exist
(recursively). *Reachable* means that the tree blob is reachable starting from
a snapshot.
- An index *must* only reference valid blobs in existing packs.
- All blobs referenced by a snapshot *should* be listed in an index.
This leads to the following recommended order to store data in a repository.
First, pack files, which contain data and tree blobs, must be written. Then the
indexes which reference blobs in these already written pack files. And finally
the corresponding snapshots.
Note that there is no need for a specific write order of data and tree blobs
during a backup as the blobs only become referenced once the corresponding
snapshot is uploaded.
Reading data should follow the opposite order compared to writing. Only once a
snapshot was written, it is guaranteed that all required data exists in the
repository. This especially means that the list of snapshots to read should be
collected before loading the repository index. The other way round can lead to
a race condition where a recently written snapshot is loaded but not its
accompanying index, which results in a failure to access the snapshot's tree
blob.
For removing or rewriting data from a repository the following rules must be
followed, which are derived from the above invariants.
- A client removing data *must* acquire an exclusive lock first to prevent
conflicts with other clients.
- A pack *must* be removed from the referencing index before it is deleted.
- Rewriting a pack *must* write the new pack, update the index (add an updated
index and delete the old one) and only then delete the old pack.
Backups and Deduplication
=========================
@@ -681,10 +584,10 @@ General assumptions:
key management design, it is impossible to securely revoke a leaked key
without re-encrypting the whole repository.
- Advances in cryptography attacks against the cryptographic primitives used
by restic (i.e., AES-256-CTR-Poly1305-AES and SHA-256) have not occurred. Such
by restic (i.e, AES-256-CTR-Poly1305-AES and SHA-256) have not occurred. Such
advances could render the confidentiality or integrity protections provided
by restic useless.
- Sufficient advances in computing have not occurred to make brute-force
- Sufficient advances in computing have not occurred to make bruteforce
attacks against restic's cryptographic protections feasible.
The restic backup program guarantees the following:
@@ -766,11 +669,3 @@ An adversary who has a leaked (decrypted) key for a repository could:
only be done using the ``copy`` command, which moves the data into a new
repository with a new master key, or by making a completely new repository
and new backup.
Changes
=======
Repository Version 2
--------------------
* Support compression for blobs (data/tree) and index / lock / snapshot files

View File

@@ -51,7 +51,7 @@ looks like this:
[0:00] 100.00% 16 / 16 snapshots
no errors were found
The message means that there is more data stored in the repository than
The message means that there is more data stored in the repo than
strictly necessary. This is uncritical. With high probability this is duplicate data
caused by an interrupted backup run or upload operation. In
order to clean it up, the command ``restic prune`` can be used.
@@ -168,8 +168,8 @@ scheduling algorithm to give it the least favorable niceness (19).
The above example makes sure that the system the backup runs on
is not slowed down, which is particularly useful for servers.
Creating new repository on a Synology NAS via sftp fails
--------------------------------------------------------
Creating new repo on a Synology NAS via sftp fails
--------------------------------------------------
For using restic with a Synology NAS via sftp, please make sure that the
specified path is absolute, it must start with a slash (``/``).

View File

@@ -18,8 +18,7 @@ function __restic_perform_completion
__restic_debug "args: $args"
__restic_debug "last arg: $lastArg"
# Disable ActiveHelp which is not supported for fish shell
set -l requestComp "RESTIC_ACTIVE_HELP=0 $args[1] __complete $args[2..-1] $lastArg"
set -l requestComp "$args[1] __complete $args[2..-1] $lastArg"
__restic_debug "Calling $requestComp"
set -l results (eval $requestComp 2> /dev/null)

View File

@@ -9,7 +9,6 @@ Restic Documentation
030_preparing_a_new_repo
040_backup
045_working_with_repos
047_tuning_backup_parameters
050_restore
060_forget
070_encryption

View File

@@ -3,12 +3,12 @@
.SH NAME
.PP
restic-backup - Create a new backup of files and/or directories
restic\-backup \- Create a new backup of files and/or directories
.SH SYNOPSIS
.PP
\fBrestic backup [flags] [FILE/DIR] ...\fP
\fBrestic backup [flags] FILE/DIR [FILE/DIR] ...\fP
.SH DESCRIPTION
@@ -26,178 +26,170 @@ Exit status is 3 if some source data could not be read (incomplete snapshot crea
.SH OPTIONS
.PP
\fB-n\fP, \fB--dry-run\fP[=false]
\fB\-n\fP, \fB\-\-dry\-run\fP[=false]
do not upload or write any data, just show what would be done
.PP
\fB-e\fP, \fB--exclude\fP=[]
\fB\-e\fP, \fB\-\-exclude\fP=[]
exclude a \fB\fCpattern\fR (can be specified multiple times)
.PP
\fB--exclude-caches\fP[=false]
\fB\-\-exclude\-caches\fP[=false]
excludes cache directories that are marked with a CACHEDIR.TAG file. See https://bford.info/cachedir/ for the Cache Directory Tagging Standard
.PP
\fB--exclude-file\fP=[]
\fB\-\-exclude\-file\fP=[]
read exclude patterns from a \fB\fCfile\fR (can be specified multiple times)
.PP
\fB--exclude-if-present\fP=[]
\fB\-\-exclude\-if\-present\fP=[]
takes \fB\fCfilename[:header]\fR, exclude contents of directories containing filename (except filename itself) if header of that file is as provided (can be specified multiple times)
.PP
\fB--exclude-larger-than\fP=""
\fB\-\-exclude\-larger\-than\fP=""
max \fB\fCsize\fR of the files to be backed up (allowed suffixes: k/K, m/M, g/G, t/T)
.PP
\fB--files-from\fP=[]
\fB\-\-files\-from\fP=[]
read the files to backup from \fB\fCfile\fR (can be combined with file args; can be specified multiple times)
.PP
\fB--files-from-raw\fP=[]
\fB\-\-files\-from\-raw\fP=[]
read the files to backup from \fB\fCfile\fR (can be combined with file args; can be specified multiple times)
.PP
\fB--files-from-verbatim\fP=[]
\fB\-\-files\-from\-verbatim\fP=[]
read the files to backup from \fB\fCfile\fR (can be combined with file args; can be specified multiple times)
.PP
\fB-f\fP, \fB--force\fP[=false]
force re-reading the target files/directories (overrides the "parent" flag)
\fB\-f\fP, \fB\-\-force\fP[=false]
force re\-reading the target files/directories (overrides the "parent" flag)
.PP
\fB-h\fP, \fB--help\fP[=false]
\fB\-h\fP, \fB\-\-help\fP[=false]
help for backup
.PP
\fB-H\fP, \fB--host\fP=""
\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
\fB--iexclude\fP=[]
same as --exclude \fB\fCpattern\fR but ignores the casing of filenames
\fB\-\-iexclude\fP=[]
same as \-\-exclude \fB\fCpattern\fR but ignores the casing of filenames
.PP
\fB--iexclude-file\fP=[]
same as --exclude-file but ignores casing of \fB\fCfile\fRnames in patterns
\fB\-\-iexclude\-file\fP=[]
same as \-\-exclude\-file but ignores casing of \fB\fCfile\fRnames in patterns
.PP
\fB--ignore-ctime\fP[=false]
\fB\-\-ignore\-ctime\fP[=false]
ignore ctime changes when checking for modified files
.PP
\fB--ignore-inode\fP[=false]
\fB\-\-ignore\-inode\fP[=false]
ignore inode number changes when checking for modified files
.PP
\fB-x\fP, \fB--one-file-system\fP[=false]
\fB\-x\fP, \fB\-\-one\-file\-system\fP[=false]
exclude other file systems, don't cross filesystem boundaries and subvolumes
.PP
\fB--parent\fP=""
use this parent \fB\fCsnapshot\fR (default: last snapshot in the repository that has the same target files/directories, and is not newer than the snapshot time)
\fB\-\-parent\fP=""
use this parent \fB\fCsnapshot\fR (default: last snapshot in the repo that has the same target files/directories, and is not newer than the snapshot time)
.PP
\fB--stdin\fP[=false]
\fB\-\-stdin\fP[=false]
read backup from stdin
.PP
\fB--stdin-filename\fP="stdin"
\fB\-\-stdin\-filename\fP="stdin"
\fB\fCfilename\fR to use when reading from stdin
.PP
\fB--tag\fP=[]
\fB\-\-tag\fP=[]
add \fB\fCtags\fR for the new snapshot in the format \fB\fCtag[,tag,...]\fR (can be specified multiple times)
.PP
\fB--time\fP=""
\fB\fCtime\fR of the backup (ex. '2012-11-01 22:08:41') (default: now)
\fB\-\-time\fP=""
\fB\fCtime\fR of the backup (ex. '2012\-11\-01 22:08:41') (default: now)
.PP
\fB--with-atime\fP[=false]
\fB\-\-with\-atime\fP[=false]
store the atime for all files and directories
.SH OPTIONS INHERITED FROM PARENT COMMANDS
.PP
\fB--cacert\fP=[]
\fB\-\-cacert\fP=[]
\fB\fCfile\fR to load root certificates from (default: use system certificates)
.PP
\fB--cache-dir\fP=""
\fB\-\-cache\-dir\fP=""
set the cache \fB\fCdirectory\fR\&. (default: use system default cache directory)
.PP
\fB--cleanup-cache\fP[=false]
\fB\-\-cleanup\-cache\fP[=false]
auto remove old cache directories
.PP
\fB--compression\fP=auto
compression mode (only available for repository format version 2), one of (auto|off|max)
\fB\-\-insecure\-tls\fP[=false]
skip TLS certificate verification when connecting to the repo (insecure)
.PP
\fB--insecure-tls\fP[=false]
skip TLS certificate verification when connecting to the repository (insecure)
.PP
\fB--json\fP[=false]
\fB\-\-json\fP[=false]
set output mode to JSON for commands that support it
.PP
\fB--key-hint\fP=""
\fB\fCkey\fR ID of key to try decrypting first (default: $RESTIC_KEY_HINT)
\fB\-\-key\-hint\fP=""
\fB\fCkey\fR ID of key to try decrypting first (default: $RESTIC\_KEY\_HINT)
.PP
\fB--limit-download\fP=0
\fB\-\-limit\-download\fP=0
limits downloads to a maximum rate in KiB/s. (default: unlimited)
.PP
\fB--limit-upload\fP=0
\fB\-\-limit\-upload\fP=0
limits uploads to a maximum rate in KiB/s. (default: unlimited)
.PP
\fB--no-cache\fP[=false]
\fB\-\-no\-cache\fP[=false]
do not use a local cache
.PP
\fB--no-lock\fP[=false]
do not lock the repository, this allows some operations on read-only repositories
\fB\-\-no\-lock\fP[=false]
do not lock the repository, this allows some operations on read\-only repositories
.PP
\fB-o\fP, \fB--option\fP=[]
\fB\-o\fP, \fB\-\-option\fP=[]
set extended option (\fB\fCkey=value\fR, can be specified multiple times)
.PP
\fB--pack-size\fP=0
set target pack size in MiB, created pack files may be larger (default: $RESTIC_PACK_SIZE)
\fB\-\-password\-command\fP=""
shell \fB\fCcommand\fR to obtain the repository password from (default: $RESTIC\_PASSWORD\_COMMAND)
.PP
\fB--password-command\fP=""
shell \fB\fCcommand\fR to obtain the repository password from (default: $RESTIC_PASSWORD_COMMAND)
\fB\-p\fP, \fB\-\-password\-file\fP=""
\fB\fCfile\fR to read the repository password from (default: $RESTIC\_PASSWORD\_FILE)
.PP
\fB-p\fP, \fB--password-file\fP=""
\fB\fCfile\fR to read the repository password from (default: $RESTIC_PASSWORD_FILE)
.PP
\fB-q\fP, \fB--quiet\fP[=false]
\fB\-q\fP, \fB\-\-quiet\fP[=false]
do not output comprehensive progress report
.PP
\fB-r\fP, \fB--repo\fP=""
\fB\fCrepository\fR to backup to or restore from (default: $RESTIC_REPOSITORY)
\fB\-r\fP, \fB\-\-repo\fP=""
\fB\fCrepository\fR to backup to or restore from (default: $RESTIC\_REPOSITORY)
.PP
\fB--repository-file\fP=""
\fB\fCfile\fR to read the repository location from (default: $RESTIC_REPOSITORY_FILE)
\fB\-\-repository\-file\fP=""
\fB\fCfile\fR to read the repository location from (default: $RESTIC\_REPOSITORY\_FILE)
.PP
\fB--tls-client-cert\fP=""
\fB\-\-tls\-client\-cert\fP=""
path to a \fB\fCfile\fR containing PEM encoded TLS client certificate and private key
.PP
\fB-v\fP, \fB--verbose\fP[=0]
be verbose (specify multiple times or a level using --verbose=\fB\fCn\fR, max level/times is 3)
\fB\-v\fP, \fB\-\-verbose\fP[=0]
be verbose (specify multiple times or a level using \-\-verbose=\fB\fCn\fR, max level/times is 3)
.SH SEE ALSO

View File

@@ -3,7 +3,7 @@
.SH NAME
.PP
restic-cache - Operate on local cache directories
restic\-cache \- Operate on local cache directories
.SH SYNOPSIS
@@ -18,107 +18,99 @@ The "cache" command allows listing and cleaning local cache directories.
.SH EXIT STATUS
.PP
Exit status is 0 if the command was successful, and non-zero if there was any error.
Exit status is 0 if the command was successful, and non\-zero if there was any error.
.SH OPTIONS
.PP
\fB--cleanup\fP[=false]
\fB\-\-cleanup\fP[=false]
remove old cache directories
.PP
\fB-h\fP, \fB--help\fP[=false]
\fB\-h\fP, \fB\-\-help\fP[=false]
help for cache
.PP
\fB--max-age\fP=30
\fB\-\-max\-age\fP=30
max age in \fB\fCdays\fR for cache directories to be considered old
.PP
\fB--no-size\fP[=false]
\fB\-\-no\-size\fP[=false]
do not output the size of the cache directories
.SH OPTIONS INHERITED FROM PARENT COMMANDS
.PP
\fB--cacert\fP=[]
\fB\-\-cacert\fP=[]
\fB\fCfile\fR to load root certificates from (default: use system certificates)
.PP
\fB--cache-dir\fP=""
\fB\-\-cache\-dir\fP=""
set the cache \fB\fCdirectory\fR\&. (default: use system default cache directory)
.PP
\fB--cleanup-cache\fP[=false]
\fB\-\-cleanup\-cache\fP[=false]
auto remove old cache directories
.PP
\fB--compression\fP=auto
compression mode (only available for repository format version 2), one of (auto|off|max)
\fB\-\-insecure\-tls\fP[=false]
skip TLS certificate verification when connecting to the repo (insecure)
.PP
\fB--insecure-tls\fP[=false]
skip TLS certificate verification when connecting to the repository (insecure)
.PP
\fB--json\fP[=false]
\fB\-\-json\fP[=false]
set output mode to JSON for commands that support it
.PP
\fB--key-hint\fP=""
\fB\fCkey\fR ID of key to try decrypting first (default: $RESTIC_KEY_HINT)
\fB\-\-key\-hint\fP=""
\fB\fCkey\fR ID of key to try decrypting first (default: $RESTIC\_KEY\_HINT)
.PP
\fB--limit-download\fP=0
\fB\-\-limit\-download\fP=0
limits downloads to a maximum rate in KiB/s. (default: unlimited)
.PP
\fB--limit-upload\fP=0
\fB\-\-limit\-upload\fP=0
limits uploads to a maximum rate in KiB/s. (default: unlimited)
.PP
\fB--no-cache\fP[=false]
\fB\-\-no\-cache\fP[=false]
do not use a local cache
.PP
\fB--no-lock\fP[=false]
do not lock the repository, this allows some operations on read-only repositories
\fB\-\-no\-lock\fP[=false]
do not lock the repository, this allows some operations on read\-only repositories
.PP
\fB-o\fP, \fB--option\fP=[]
\fB\-o\fP, \fB\-\-option\fP=[]
set extended option (\fB\fCkey=value\fR, can be specified multiple times)
.PP
\fB--pack-size\fP=0
set target pack size in MiB, created pack files may be larger (default: $RESTIC_PACK_SIZE)
\fB\-\-password\-command\fP=""
shell \fB\fCcommand\fR to obtain the repository password from (default: $RESTIC\_PASSWORD\_COMMAND)
.PP
\fB--password-command\fP=""
shell \fB\fCcommand\fR to obtain the repository password from (default: $RESTIC_PASSWORD_COMMAND)
\fB\-p\fP, \fB\-\-password\-file\fP=""
\fB\fCfile\fR to read the repository password from (default: $RESTIC\_PASSWORD\_FILE)
.PP
\fB-p\fP, \fB--password-file\fP=""
\fB\fCfile\fR to read the repository password from (default: $RESTIC_PASSWORD_FILE)
.PP
\fB-q\fP, \fB--quiet\fP[=false]
\fB\-q\fP, \fB\-\-quiet\fP[=false]
do not output comprehensive progress report
.PP
\fB-r\fP, \fB--repo\fP=""
\fB\fCrepository\fR to backup to or restore from (default: $RESTIC_REPOSITORY)
\fB\-r\fP, \fB\-\-repo\fP=""
\fB\fCrepository\fR to backup to or restore from (default: $RESTIC\_REPOSITORY)
.PP
\fB--repository-file\fP=""
\fB\fCfile\fR to read the repository location from (default: $RESTIC_REPOSITORY_FILE)
\fB\-\-repository\-file\fP=""
\fB\fCfile\fR to read the repository location from (default: $RESTIC\_REPOSITORY\_FILE)
.PP
\fB--tls-client-cert\fP=""
\fB\-\-tls\-client\-cert\fP=""
path to a \fB\fCfile\fR containing PEM encoded TLS client certificate and private key
.PP
\fB-v\fP, \fB--verbose\fP[=0]
be verbose (specify multiple times or a level using --verbose=\fB\fCn\fR, max level/times is 3)
\fB\-v\fP, \fB\-\-verbose\fP[=0]
be verbose (specify multiple times or a level using \-\-verbose=\fB\fCn\fR, max level/times is 3)
.SH SEE ALSO

View File

@@ -3,7 +3,7 @@
.SH NAME
.PP
restic-cat - Print internal objects to stdout
restic\-cat \- Print internal objects to stdout
.SH SYNOPSIS
@@ -18,95 +18,87 @@ The "cat" command is used to print internal objects to stdout.
.SH EXIT STATUS
.PP
Exit status is 0 if the command was successful, and non-zero if there was any error.
Exit status is 0 if the command was successful, and non\-zero if there was any error.
.SH OPTIONS
.PP
\fB-h\fP, \fB--help\fP[=false]
\fB\-h\fP, \fB\-\-help\fP[=false]
help for cat
.SH OPTIONS INHERITED FROM PARENT COMMANDS
.PP
\fB--cacert\fP=[]
\fB\-\-cacert\fP=[]
\fB\fCfile\fR to load root certificates from (default: use system certificates)
.PP
\fB--cache-dir\fP=""
\fB\-\-cache\-dir\fP=""
set the cache \fB\fCdirectory\fR\&. (default: use system default cache directory)
.PP
\fB--cleanup-cache\fP[=false]
\fB\-\-cleanup\-cache\fP[=false]
auto remove old cache directories
.PP
\fB--compression\fP=auto
compression mode (only available for repository format version 2), one of (auto|off|max)
\fB\-\-insecure\-tls\fP[=false]
skip TLS certificate verification when connecting to the repo (insecure)
.PP
\fB--insecure-tls\fP[=false]
skip TLS certificate verification when connecting to the repository (insecure)
.PP
\fB--json\fP[=false]
\fB\-\-json\fP[=false]
set output mode to JSON for commands that support it
.PP
\fB--key-hint\fP=""
\fB\fCkey\fR ID of key to try decrypting first (default: $RESTIC_KEY_HINT)
\fB\-\-key\-hint\fP=""
\fB\fCkey\fR ID of key to try decrypting first (default: $RESTIC\_KEY\_HINT)
.PP
\fB--limit-download\fP=0
\fB\-\-limit\-download\fP=0
limits downloads to a maximum rate in KiB/s. (default: unlimited)
.PP
\fB--limit-upload\fP=0
\fB\-\-limit\-upload\fP=0
limits uploads to a maximum rate in KiB/s. (default: unlimited)
.PP
\fB--no-cache\fP[=false]
\fB\-\-no\-cache\fP[=false]
do not use a local cache
.PP
\fB--no-lock\fP[=false]
do not lock the repository, this allows some operations on read-only repositories
\fB\-\-no\-lock\fP[=false]
do not lock the repository, this allows some operations on read\-only repositories
.PP
\fB-o\fP, \fB--option\fP=[]
\fB\-o\fP, \fB\-\-option\fP=[]
set extended option (\fB\fCkey=value\fR, can be specified multiple times)
.PP
\fB--pack-size\fP=0
set target pack size in MiB, created pack files may be larger (default: $RESTIC_PACK_SIZE)
\fB\-\-password\-command\fP=""
shell \fB\fCcommand\fR to obtain the repository password from (default: $RESTIC\_PASSWORD\_COMMAND)
.PP
\fB--password-command\fP=""
shell \fB\fCcommand\fR to obtain the repository password from (default: $RESTIC_PASSWORD_COMMAND)
\fB\-p\fP, \fB\-\-password\-file\fP=""
\fB\fCfile\fR to read the repository password from (default: $RESTIC\_PASSWORD\_FILE)
.PP
\fB-p\fP, \fB--password-file\fP=""
\fB\fCfile\fR to read the repository password from (default: $RESTIC_PASSWORD_FILE)
.PP
\fB-q\fP, \fB--quiet\fP[=false]
\fB\-q\fP, \fB\-\-quiet\fP[=false]
do not output comprehensive progress report
.PP
\fB-r\fP, \fB--repo\fP=""
\fB\fCrepository\fR to backup to or restore from (default: $RESTIC_REPOSITORY)
\fB\-r\fP, \fB\-\-repo\fP=""
\fB\fCrepository\fR to backup to or restore from (default: $RESTIC\_REPOSITORY)
.PP
\fB--repository-file\fP=""
\fB\fCfile\fR to read the repository location from (default: $RESTIC_REPOSITORY_FILE)
\fB\-\-repository\-file\fP=""
\fB\fCfile\fR to read the repository location from (default: $RESTIC\_REPOSITORY\_FILE)
.PP
\fB--tls-client-cert\fP=""
\fB\-\-tls\-client\-cert\fP=""
path to a \fB\fCfile\fR containing PEM encoded TLS client certificate and private key
.PP
\fB-v\fP, \fB--verbose\fP[=0]
be verbose (specify multiple times or a level using --verbose=\fB\fCn\fR, max level/times is 3)
\fB\-v\fP, \fB\-\-verbose\fP[=0]
be verbose (specify multiple times or a level using \-\-verbose=\fB\fCn\fR, max level/times is 3)
.SH SEE ALSO

View File

@@ -3,7 +3,7 @@
.SH NAME
.PP
restic-check - Check the repository for errors
restic\-check \- Check the repository for errors
.SH SYNOPSIS
@@ -23,107 +23,103 @@ repository and not use a local cache.
.SH EXIT STATUS
.PP
Exit status is 0 if the command was successful, and non-zero if there was any error.
Exit status is 0 if the command was successful, and non\-zero if there was any error.
.SH OPTIONS
.PP
\fB-h\fP, \fB--help\fP[=false]
\fB\-\-check\-unused\fP[=false]
find unused blobs
.PP
\fB\-h\fP, \fB\-\-help\fP[=false]
help for check
.PP
\fB--read-data\fP[=false]
\fB\-\-read\-data\fP[=false]
read all data blobs
.PP
\fB--read-data-subset\fP=""
\fB\-\-read\-data\-subset\fP=""
read a \fB\fCsubset\fR of data packs, specified as 'n/t' for specific part, or either 'x%' or 'x.y%' or a size in bytes with suffixes k/K, m/M, g/G, t/T for a random subset
.PP
\fB--with-cache\fP[=false]
\fB\-\-with\-cache\fP[=false]
use the cache
.SH OPTIONS INHERITED FROM PARENT COMMANDS
.PP
\fB--cacert\fP=[]
\fB\-\-cacert\fP=[]
\fB\fCfile\fR to load root certificates from (default: use system certificates)
.PP
\fB--cache-dir\fP=""
\fB\-\-cache\-dir\fP=""
set the cache \fB\fCdirectory\fR\&. (default: use system default cache directory)
.PP
\fB--cleanup-cache\fP[=false]
\fB\-\-cleanup\-cache\fP[=false]
auto remove old cache directories
.PP
\fB--compression\fP=auto
compression mode (only available for repository format version 2), one of (auto|off|max)
\fB\-\-insecure\-tls\fP[=false]
skip TLS certificate verification when connecting to the repo (insecure)
.PP
\fB--insecure-tls\fP[=false]
skip TLS certificate verification when connecting to the repository (insecure)
.PP
\fB--json\fP[=false]
\fB\-\-json\fP[=false]
set output mode to JSON for commands that support it
.PP
\fB--key-hint\fP=""
\fB\fCkey\fR ID of key to try decrypting first (default: $RESTIC_KEY_HINT)
\fB\-\-key\-hint\fP=""
\fB\fCkey\fR ID of key to try decrypting first (default: $RESTIC\_KEY\_HINT)
.PP
\fB--limit-download\fP=0
\fB\-\-limit\-download\fP=0
limits downloads to a maximum rate in KiB/s. (default: unlimited)
.PP
\fB--limit-upload\fP=0
\fB\-\-limit\-upload\fP=0
limits uploads to a maximum rate in KiB/s. (default: unlimited)
.PP
\fB--no-cache\fP[=false]
\fB\-\-no\-cache\fP[=false]
do not use a local cache
.PP
\fB--no-lock\fP[=false]
do not lock the repository, this allows some operations on read-only repositories
\fB\-\-no\-lock\fP[=false]
do not lock the repository, this allows some operations on read\-only repositories
.PP
\fB-o\fP, \fB--option\fP=[]
\fB\-o\fP, \fB\-\-option\fP=[]
set extended option (\fB\fCkey=value\fR, can be specified multiple times)
.PP
\fB--pack-size\fP=0
set target pack size in MiB, created pack files may be larger (default: $RESTIC_PACK_SIZE)
\fB\-\-password\-command\fP=""
shell \fB\fCcommand\fR to obtain the repository password from (default: $RESTIC\_PASSWORD\_COMMAND)
.PP
\fB--password-command\fP=""
shell \fB\fCcommand\fR to obtain the repository password from (default: $RESTIC_PASSWORD_COMMAND)
\fB\-p\fP, \fB\-\-password\-file\fP=""
\fB\fCfile\fR to read the repository password from (default: $RESTIC\_PASSWORD\_FILE)
.PP
\fB-p\fP, \fB--password-file\fP=""
\fB\fCfile\fR to read the repository password from (default: $RESTIC_PASSWORD_FILE)
.PP
\fB-q\fP, \fB--quiet\fP[=false]
\fB\-q\fP, \fB\-\-quiet\fP[=false]
do not output comprehensive progress report
.PP
\fB-r\fP, \fB--repo\fP=""
\fB\fCrepository\fR to backup to or restore from (default: $RESTIC_REPOSITORY)
\fB\-r\fP, \fB\-\-repo\fP=""
\fB\fCrepository\fR to backup to or restore from (default: $RESTIC\_REPOSITORY)
.PP
\fB--repository-file\fP=""
\fB\fCfile\fR to read the repository location from (default: $RESTIC_REPOSITORY_FILE)
\fB\-\-repository\-file\fP=""
\fB\fCfile\fR to read the repository location from (default: $RESTIC\_REPOSITORY\_FILE)
.PP
\fB--tls-client-cert\fP=""
\fB\-\-tls\-client\-cert\fP=""
path to a \fB\fCfile\fR containing PEM encoded TLS client certificate and private key
.PP
\fB-v\fP, \fB--verbose\fP[=0]
be verbose (specify multiple times or a level using --verbose=\fB\fCn\fR, max level/times is 3)
\fB\-v\fP, \fB\-\-verbose\fP[=0]
be verbose (specify multiple times or a level using \-\-verbose=\fB\fCn\fR, max level/times is 3)
.SH SEE ALSO

View File

@@ -3,7 +3,7 @@
.SH NAME
.PP
restic-copy - Copy snapshots from one repository to another
restic\-copy \- Copy snapshots from one repository to another
.SH SYNOPSIS
@@ -22,132 +22,124 @@ destination repositories. This /may incur higher bandwidth usage and costs/ than
expected during normal backup runs.
.PP
NOTE: The copying process does not re-chunk files, which may break deduplication
NOTE: The copying process does not re\-chunk files, which may break deduplication
between the files copied and files already stored in the destination repository.
This means that copied files, which existed in both the source and destination
repository, /may occupy up to twice their space/ in the destination repository.
This can be mitigated by the "--copy-chunker-params" option when initializing a
This can be mitigated by the "\-\-copy\-chunker\-params" option when initializing a
new destination repository using the "init" command.
.SH OPTIONS
.PP
\fB--from-key-hint\fP=""
key ID of key to try decrypting the source repository first (default: $RESTIC_FROM_KEY_HINT)
.PP
\fB--from-password-command\fP=""
shell \fB\fCcommand\fR to obtain the source repository password from (default: $RESTIC_FROM_PASSWORD_COMMAND)
.PP
\fB--from-password-file\fP=""
\fB\fCfile\fR to read the source repository password from (default: $RESTIC_FROM_PASSWORD_FILE)
.PP
\fB--from-repo\fP=""
source \fB\fCrepository\fR to copy snapshots from (default: $RESTIC_FROM_REPOSITORY)
.PP
\fB--from-repository-file\fP=""
\fB\fCfile\fR from which to read the source repository location to copy snapshots from (default: $RESTIC_FROM_REPOSITORY_FILE)
.PP
\fB-h\fP, \fB--help\fP[=false]
\fB\-h\fP, \fB\-\-help\fP[=false]
help for copy
.PP
\fB-H\fP, \fB--host\fP=[]
\fB\-H\fP, \fB\-\-host\fP=[]
only consider snapshots for this \fB\fChost\fR, when no snapshot ID is given (can be specified multiple times)
.PP
\fB--path\fP=[]
\fB\-\-key\-hint2\fP=""
key ID of key to try decrypting the destination repository first (default: $RESTIC\_KEY\_HINT2)
.PP
\fB\-\-password\-command2\fP=""
shell \fB\fCcommand\fR to obtain the destination repository password from (default: $RESTIC\_PASSWORD\_COMMAND2)
.PP
\fB\-\-password\-file2\fP=""
\fB\fCfile\fR to read the destination repository password from (default: $RESTIC\_PASSWORD\_FILE2)
.PP
\fB\-\-path\fP=[]
only consider snapshots which include this (absolute) \fB\fCpath\fR, when no snapshot ID is given
.PP
\fB--tag\fP=[]
\fB\-\-repo2\fP=""
destination \fB\fCrepository\fR to copy snapshots to (default: $RESTIC\_REPOSITORY2)
.PP
\fB\-\-repository\-file2\fP=""
\fB\fCfile\fR from which to read the destination repository location to copy snapshots to (default: $RESTIC\_REPOSITORY\_FILE2)
.PP
\fB\-\-tag\fP=[]
only consider snapshots which include this \fB\fCtaglist\fR, when no snapshot ID is given
.SH OPTIONS INHERITED FROM PARENT COMMANDS
.PP
\fB--cacert\fP=[]
\fB\-\-cacert\fP=[]
\fB\fCfile\fR to load root certificates from (default: use system certificates)
.PP
\fB--cache-dir\fP=""
\fB\-\-cache\-dir\fP=""
set the cache \fB\fCdirectory\fR\&. (default: use system default cache directory)
.PP
\fB--cleanup-cache\fP[=false]
\fB\-\-cleanup\-cache\fP[=false]
auto remove old cache directories
.PP
\fB--compression\fP=auto
compression mode (only available for repository format version 2), one of (auto|off|max)
\fB\-\-insecure\-tls\fP[=false]
skip TLS certificate verification when connecting to the repo (insecure)
.PP
\fB--insecure-tls\fP[=false]
skip TLS certificate verification when connecting to the repository (insecure)
.PP
\fB--json\fP[=false]
\fB\-\-json\fP[=false]
set output mode to JSON for commands that support it
.PP
\fB--key-hint\fP=""
\fB\fCkey\fR ID of key to try decrypting first (default: $RESTIC_KEY_HINT)
\fB\-\-key\-hint\fP=""
\fB\fCkey\fR ID of key to try decrypting first (default: $RESTIC\_KEY\_HINT)
.PP
\fB--limit-download\fP=0
\fB\-\-limit\-download\fP=0
limits downloads to a maximum rate in KiB/s. (default: unlimited)
.PP
\fB--limit-upload\fP=0
\fB\-\-limit\-upload\fP=0
limits uploads to a maximum rate in KiB/s. (default: unlimited)
.PP
\fB--no-cache\fP[=false]
\fB\-\-no\-cache\fP[=false]
do not use a local cache
.PP
\fB--no-lock\fP[=false]
do not lock the repository, this allows some operations on read-only repositories
\fB\-\-no\-lock\fP[=false]
do not lock the repository, this allows some operations on read\-only repositories
.PP
\fB-o\fP, \fB--option\fP=[]
\fB\-o\fP, \fB\-\-option\fP=[]
set extended option (\fB\fCkey=value\fR, can be specified multiple times)
.PP
\fB--pack-size\fP=0
set target pack size in MiB, created pack files may be larger (default: $RESTIC_PACK_SIZE)
\fB\-\-password\-command\fP=""
shell \fB\fCcommand\fR to obtain the repository password from (default: $RESTIC\_PASSWORD\_COMMAND)
.PP
\fB--password-command\fP=""
shell \fB\fCcommand\fR to obtain the repository password from (default: $RESTIC_PASSWORD_COMMAND)
\fB\-p\fP, \fB\-\-password\-file\fP=""
\fB\fCfile\fR to read the repository password from (default: $RESTIC\_PASSWORD\_FILE)
.PP
\fB-p\fP, \fB--password-file\fP=""
\fB\fCfile\fR to read the repository password from (default: $RESTIC_PASSWORD_FILE)
.PP
\fB-q\fP, \fB--quiet\fP[=false]
\fB\-q\fP, \fB\-\-quiet\fP[=false]
do not output comprehensive progress report
.PP
\fB-r\fP, \fB--repo\fP=""
\fB\fCrepository\fR to backup to or restore from (default: $RESTIC_REPOSITORY)
\fB\-r\fP, \fB\-\-repo\fP=""
\fB\fCrepository\fR to backup to or restore from (default: $RESTIC\_REPOSITORY)
.PP
\fB--repository-file\fP=""
\fB\fCfile\fR to read the repository location from (default: $RESTIC_REPOSITORY_FILE)
\fB\-\-repository\-file\fP=""
\fB\fCfile\fR to read the repository location from (default: $RESTIC\_REPOSITORY\_FILE)
.PP
\fB--tls-client-cert\fP=""
\fB\-\-tls\-client\-cert\fP=""
path to a \fB\fCfile\fR containing PEM encoded TLS client certificate and private key
.PP
\fB-v\fP, \fB--verbose\fP[=0]
be verbose (specify multiple times or a level using --verbose=\fB\fCn\fR, max level/times is 3)
\fB\-v\fP, \fB\-\-verbose\fP[=0]
be verbose (specify multiple times or a level using \-\-verbose=\fB\fCn\fR, max level/times is 3)
.SH SEE ALSO

View File

@@ -3,12 +3,12 @@
.SH NAME
.PP
restic-diff - Show differences between two snapshots
restic\-diff \- Show differences between two snapshots
.SH SYNOPSIS
.PP
\fBrestic diff [flags] snapshot-ID snapshot-ID\fP
\fBrestic diff [flags] snapshot\-ID snapshot\-ID\fP
.SH DESCRIPTION
@@ -21,7 +21,7 @@ directory:
.IP \(bu 2
+ The item was added
.IP \(bu 2
- The item was removed
\- The item was removed
.IP \(bu 2
U The metadata (access mode, timestamps, ...) for the item was updated
.IP \(bu 2
@@ -34,99 +34,91 @@ T The type was changed, e.g. a file was made a symlink
.SH EXIT STATUS
.PP
Exit status is 0 if the command was successful, and non-zero if there was any error.
Exit status is 0 if the command was successful, and non\-zero if there was any error.
.SH OPTIONS
.PP
\fB-h\fP, \fB--help\fP[=false]
\fB\-h\fP, \fB\-\-help\fP[=false]
help for diff
.PP
\fB--metadata\fP[=false]
\fB\-\-metadata\fP[=false]
print changes in metadata
.SH OPTIONS INHERITED FROM PARENT COMMANDS
.PP
\fB--cacert\fP=[]
\fB\-\-cacert\fP=[]
\fB\fCfile\fR to load root certificates from (default: use system certificates)
.PP
\fB--cache-dir\fP=""
\fB\-\-cache\-dir\fP=""
set the cache \fB\fCdirectory\fR\&. (default: use system default cache directory)
.PP
\fB--cleanup-cache\fP[=false]
\fB\-\-cleanup\-cache\fP[=false]
auto remove old cache directories
.PP
\fB--compression\fP=auto
compression mode (only available for repository format version 2), one of (auto|off|max)
\fB\-\-insecure\-tls\fP[=false]
skip TLS certificate verification when connecting to the repo (insecure)
.PP
\fB--insecure-tls\fP[=false]
skip TLS certificate verification when connecting to the repository (insecure)
.PP
\fB--json\fP[=false]
\fB\-\-json\fP[=false]
set output mode to JSON for commands that support it
.PP
\fB--key-hint\fP=""
\fB\fCkey\fR ID of key to try decrypting first (default: $RESTIC_KEY_HINT)
\fB\-\-key\-hint\fP=""
\fB\fCkey\fR ID of key to try decrypting first (default: $RESTIC\_KEY\_HINT)
.PP
\fB--limit-download\fP=0
\fB\-\-limit\-download\fP=0
limits downloads to a maximum rate in KiB/s. (default: unlimited)
.PP
\fB--limit-upload\fP=0
\fB\-\-limit\-upload\fP=0
limits uploads to a maximum rate in KiB/s. (default: unlimited)
.PP
\fB--no-cache\fP[=false]
\fB\-\-no\-cache\fP[=false]
do not use a local cache
.PP
\fB--no-lock\fP[=false]
do not lock the repository, this allows some operations on read-only repositories
\fB\-\-no\-lock\fP[=false]
do not lock the repository, this allows some operations on read\-only repositories
.PP
\fB-o\fP, \fB--option\fP=[]
\fB\-o\fP, \fB\-\-option\fP=[]
set extended option (\fB\fCkey=value\fR, can be specified multiple times)
.PP
\fB--pack-size\fP=0
set target pack size in MiB, created pack files may be larger (default: $RESTIC_PACK_SIZE)
\fB\-\-password\-command\fP=""
shell \fB\fCcommand\fR to obtain the repository password from (default: $RESTIC\_PASSWORD\_COMMAND)
.PP
\fB--password-command\fP=""
shell \fB\fCcommand\fR to obtain the repository password from (default: $RESTIC_PASSWORD_COMMAND)
\fB\-p\fP, \fB\-\-password\-file\fP=""
\fB\fCfile\fR to read the repository password from (default: $RESTIC\_PASSWORD\_FILE)
.PP
\fB-p\fP, \fB--password-file\fP=""
\fB\fCfile\fR to read the repository password from (default: $RESTIC_PASSWORD_FILE)
.PP
\fB-q\fP, \fB--quiet\fP[=false]
\fB\-q\fP, \fB\-\-quiet\fP[=false]
do not output comprehensive progress report
.PP
\fB-r\fP, \fB--repo\fP=""
\fB\fCrepository\fR to backup to or restore from (default: $RESTIC_REPOSITORY)
\fB\-r\fP, \fB\-\-repo\fP=""
\fB\fCrepository\fR to backup to or restore from (default: $RESTIC\_REPOSITORY)
.PP
\fB--repository-file\fP=""
\fB\fCfile\fR to read the repository location from (default: $RESTIC_REPOSITORY_FILE)
\fB\-\-repository\-file\fP=""
\fB\fCfile\fR to read the repository location from (default: $RESTIC\_REPOSITORY\_FILE)
.PP
\fB--tls-client-cert\fP=""
\fB\-\-tls\-client\-cert\fP=""
path to a \fB\fCfile\fR containing PEM encoded TLS client certificate and private key
.PP
\fB-v\fP, \fB--verbose\fP[=0]
be verbose (specify multiple times or a level using --verbose=\fB\fCn\fR, max level/times is 3)
\fB\-v\fP, \fB\-\-verbose\fP[=0]
be verbose (specify multiple times or a level using \-\-verbose=\fB\fCn\fR, max level/times is 3)
.SH SEE ALSO

View File

@@ -3,7 +3,7 @@
.SH NAME
.PP
restic-dump - Print a backed-up file to stdout
restic\-dump \- Print a backed\-up file to stdout
.SH SYNOPSIS
@@ -25,111 +25,103 @@ repository.
.SH EXIT STATUS
.PP
Exit status is 0 if the command was successful, and non-zero if there was any error.
Exit status is 0 if the command was successful, and non\-zero if there was any error.
.SH OPTIONS
.PP
\fB-a\fP, \fB--archive\fP="tar"
\fB\-a\fP, \fB\-\-archive\fP="tar"
set archive \fB\fCformat\fR as "tar" or "zip"
.PP
\fB-h\fP, \fB--help\fP[=false]
\fB\-h\fP, \fB\-\-help\fP[=false]
help for dump
.PP
\fB-H\fP, \fB--host\fP=[]
\fB\-H\fP, \fB\-\-host\fP=[]
only consider snapshots for this host when the snapshot ID is "latest" (can be specified multiple times)
.PP
\fB--path\fP=[]
\fB\-\-path\fP=[]
only consider snapshots which include this (absolute) \fB\fCpath\fR for snapshot ID "latest"
.PP
\fB--tag\fP=[]
\fB\-\-tag\fP=[]
only consider snapshots which include this \fB\fCtaglist\fR for snapshot ID "latest"
.SH OPTIONS INHERITED FROM PARENT COMMANDS
.PP
\fB--cacert\fP=[]
\fB\-\-cacert\fP=[]
\fB\fCfile\fR to load root certificates from (default: use system certificates)
.PP
\fB--cache-dir\fP=""
\fB\-\-cache\-dir\fP=""
set the cache \fB\fCdirectory\fR\&. (default: use system default cache directory)
.PP
\fB--cleanup-cache\fP[=false]
\fB\-\-cleanup\-cache\fP[=false]
auto remove old cache directories
.PP
\fB--compression\fP=auto
compression mode (only available for repository format version 2), one of (auto|off|max)
\fB\-\-insecure\-tls\fP[=false]
skip TLS certificate verification when connecting to the repo (insecure)
.PP
\fB--insecure-tls\fP[=false]
skip TLS certificate verification when connecting to the repository (insecure)
.PP
\fB--json\fP[=false]
\fB\-\-json\fP[=false]
set output mode to JSON for commands that support it
.PP
\fB--key-hint\fP=""
\fB\fCkey\fR ID of key to try decrypting first (default: $RESTIC_KEY_HINT)
\fB\-\-key\-hint\fP=""
\fB\fCkey\fR ID of key to try decrypting first (default: $RESTIC\_KEY\_HINT)
.PP
\fB--limit-download\fP=0
\fB\-\-limit\-download\fP=0
limits downloads to a maximum rate in KiB/s. (default: unlimited)
.PP
\fB--limit-upload\fP=0
\fB\-\-limit\-upload\fP=0
limits uploads to a maximum rate in KiB/s. (default: unlimited)
.PP
\fB--no-cache\fP[=false]
\fB\-\-no\-cache\fP[=false]
do not use a local cache
.PP
\fB--no-lock\fP[=false]
do not lock the repository, this allows some operations on read-only repositories
\fB\-\-no\-lock\fP[=false]
do not lock the repository, this allows some operations on read\-only repositories
.PP
\fB-o\fP, \fB--option\fP=[]
\fB\-o\fP, \fB\-\-option\fP=[]
set extended option (\fB\fCkey=value\fR, can be specified multiple times)
.PP
\fB--pack-size\fP=0
set target pack size in MiB, created pack files may be larger (default: $RESTIC_PACK_SIZE)
\fB\-\-password\-command\fP=""
shell \fB\fCcommand\fR to obtain the repository password from (default: $RESTIC\_PASSWORD\_COMMAND)
.PP
\fB--password-command\fP=""
shell \fB\fCcommand\fR to obtain the repository password from (default: $RESTIC_PASSWORD_COMMAND)
\fB\-p\fP, \fB\-\-password\-file\fP=""
\fB\fCfile\fR to read the repository password from (default: $RESTIC\_PASSWORD\_FILE)
.PP
\fB-p\fP, \fB--password-file\fP=""
\fB\fCfile\fR to read the repository password from (default: $RESTIC_PASSWORD_FILE)
.PP
\fB-q\fP, \fB--quiet\fP[=false]
\fB\-q\fP, \fB\-\-quiet\fP[=false]
do not output comprehensive progress report
.PP
\fB-r\fP, \fB--repo\fP=""
\fB\fCrepository\fR to backup to or restore from (default: $RESTIC_REPOSITORY)
\fB\-r\fP, \fB\-\-repo\fP=""
\fB\fCrepository\fR to backup to or restore from (default: $RESTIC\_REPOSITORY)
.PP
\fB--repository-file\fP=""
\fB\fCfile\fR to read the repository location from (default: $RESTIC_REPOSITORY_FILE)
\fB\-\-repository\-file\fP=""
\fB\fCfile\fR to read the repository location from (default: $RESTIC\_REPOSITORY\_FILE)
.PP
\fB--tls-client-cert\fP=""
\fB\-\-tls\-client\-cert\fP=""
path to a \fB\fCfile\fR containing PEM encoded TLS client certificate and private key
.PP
\fB-v\fP, \fB--verbose\fP[=0]
be verbose (specify multiple times or a level using --verbose=\fB\fCn\fR, max level/times is 3)
\fB\-v\fP, \fB\-\-verbose\fP[=0]
be verbose (specify multiple times or a level using \-\-verbose=\fB\fCn\fR, max level/times is 3)
.SH SEE ALSO

Some files were not shown because too many files have changed in this diff Show More