Compare commits

..

565 Commits

Author SHA1 Message Date
Alexander Neumann
c02923fbfc Add VERSION for 0.8.0 2017-11-26 19:46:16 +01:00
Alexander Neumann
7c5ce83044 Update manpages and auto-completion 2017-11-26 19:46:16 +01:00
Alexander Neumann
37e2e9a844 Add version to CHANGELOG 2017-11-26 19:46:01 +01:00
Alexander Neumann
26e5db1849 Merge pull request #1446 from restic/fix-relative-restore
restore: Fix restore to relative path
2017-11-26 19:03:34 +01:00
Alexander Neumann
a2766ffe0c CI: return directly after an error occurred 2017-11-26 18:50:40 +01:00
Alexander Neumann
0f5e38609f restore: Fix restore to relative path 2017-11-26 18:36:48 +01:00
Alexander Neumann
f178cbf93d Merge pull request #1445 from restic/clean-node-name
restorer: Clean node names
2017-11-26 17:34:05 +01:00
Alexander Neumann
c8096ca8d2 Add entry to CHANGELOG 2017-11-26 15:31:09 +01:00
Alexander Neumann
27d29b9853 restorer: Make sure node names are clean 2017-11-26 15:28:45 +01:00
Alexander Neumann
8a171731ba restorer: Add tests for invalid node names 2017-11-26 15:28:37 +01:00
Alexander Neumann
abde9e2fba doc: Add --cacert to REST section 2017-11-26 10:09:54 +01:00
Alexander Neumann
6a4a328bbc Merge pull request #1443 from restic/fix-init-rest-cacert
rest: Use client for creating the repository
2017-11-25 22:12:18 +01:00
Alexander Neumann
8253fadc96 doc: Fix typo 2017-11-25 22:11:47 +01:00
Alexander Neumann
134abbd82b rest: Use client for creating the repository
Before, creating a new repo via REST would use the defaut HTTP client,
which is not a problem unless the server uses HTTPS and a TLS
certificate which isn't signed by a CA in the system's CA store. In this
case, all commands work except the 'init' command, which fails with a
message like "invalid certificate".
2017-11-25 20:56:40 +01:00
Alexander Neumann
cd8226130a Improve issue template 2017-11-25 11:43:44 +01:00
Alexander Neumann
1ebf0e8de8 Merge pull request #1437 from restic/fix-1292
s3: Document and remove default prefix
2017-11-25 11:34:26 +01:00
Alexander Neumann
0fdb9a6129 Merge pull request #1426 from pmkane/master
fix the signal name for progress reports in the docs
2017-11-24 21:58:11 +01:00
Alexander Neumann
47b326b7b5 Merge pull request #1423 from harshavardhana/creds
Fix chaining of credentials for minio-go
2017-11-24 21:57:52 +01:00
Alexander Neumann
e2cf6eb434 Merge pull request #1428 from stephengroat/patch-1
use default brew formula
2017-11-24 21:38:04 +01:00
Alexander Neumann
f79698dcdd Merge pull request #1410 from armhold/deadlock2
unify behavior for max http connections across backends
2017-11-24 21:32:56 +01:00
Alexander Neumann
35a5307db3 Merge pull request #1415 from armhold/signals
also handle SIGPIPE in cleanup routines
2017-11-24 21:28:08 +01:00
Alexander Neumann
014cec06f1 Add entry to CHANGELOG 2017-11-21 21:33:09 +01:00
Alexander Neumann
431ab5aa6a manual: Add hint about old default prefix 2017-11-21 21:33:09 +01:00
Alexander Neumann
262b0cd9d4 s3: Remove default prefix "/restic" 2017-11-21 21:33:09 +01:00
Alexander Neumann
e83ec17e95 s3: Correct comment 2017-11-20 22:21:39 +01:00
Stephen
e2005e02bb use default brew formula 2017-11-18 08:02:41 -08:00
Harshavardhana
41c8c946ba Fix chaining of credentials for minio-go
chaining failed because chaining provider
was only looking for subsequent credentials
provider after an error. Writer a new
chaining provider which proceeds to fetch
new credentials also under situations where
providers do not return but instead return
no keys at all.

Fixes https://github.com/restic/restic/issues/1422
2017-11-18 02:51:12 -08:00
Patrick Kane
fe08686558 fix the signal name for progress reports in the docs 2017-11-16 19:17:25 -08:00
George Armhold
0ed2401711 exit 1 if received signal is other than SIGINT
send cleanup msg to stderr, not stdout
gh-1413
2017-11-09 07:16:01 -05:00
Alexander Neumann
06bd606d85 Merge pull request #1414 from bket/1307
Fix test failing on OpenBSD #1307
2017-11-08 18:54:44 +01:00
George Armhold
c347431907 also handle SIGPIPE in cleanup routines
fixes gh-1413: restic fails to cleanup locks when bash pipeline fails
2017-11-05 07:14:27 -05:00
Björn Ketelaars
f63d7048f9 Fix test failing on OpenBSD #1307
Is seems that #1307 is similar to #1087, which describes a comparable
observation on Apple's new filesystem. #1389 Has been committed and
fixes the problem on Darwin.

Although I'm not sure if the root cause of the issue is the same the
solution is similar for OpenBSD, and leverages #1389.
2017-11-05 07:27:58 +01:00
Alexander Neumann
f39f7c76dd Merge pull request #1406 from michaeldorner/master
Minor fixes on README and LICENSE
2017-11-04 09:58:21 +01:00
George Armhold
0268d0e7d6 swift backend: limit http concurrency in Save(), Stat(), Test(), Remove(),
List().

move comment regarding problematic List() backend api (it's s3's ListObjects
that has a problem, NOT swift's ObjectsWalk).

As per discussion in PR #1399.
2017-11-02 18:29:32 -04:00
George Armhold
8515d093e0 swift backend: fix premature release of semaphore in Load() & document
concurrency issue in List().

refactor wrapReader from b2 -> semaphore so it can be used elsewhere.

As per discussion in PR #1399.
2017-11-02 12:38:17 -04:00
Michael Dorner
fe3f326d8d Update LICENSE
- improved formatting of license information (BSD 2-clase) so Github can automatically detect it.
2017-11-02 11:39:49 +01:00
Michael Dorner
8170db40c7 Update README.rst
- added Codecov badge
- added link to OSI for the license
2017-11-02 11:36:25 +01:00
George Armhold
99ac0da4bc s3 backend: limit http concurrency in Save(), Stat(), Test(), Remove()
NB: List() is NOT currently limited, as it would cause deadlock due to
be.client.ListObjects() implementation.

as per discussion in PR #1399
2017-11-01 09:40:54 -04:00
Alexander Neumann
7e2c93420f Merge pull request #1397 from restic/crypto-aead
crypto: Make crypto.Key implement cipher.AEAD
2017-11-01 13:21:10 +01:00
Alexander Neumann
6d46824fb0 Pass in a nil buffer to Open() 2017-11-01 10:30:40 +01:00
Alexander Neumann
bb435b39d9 crypto: Rework Seal/Open to use sliceForAppend 2017-11-01 10:30:40 +01:00
Alexander Neumann
2a67d7a6c2 crypto: Correct test function names 2017-11-01 10:30:40 +01:00
Alexander Neumann
ba43c8bab5 crypto: Fix nonce test, make it faster 2017-11-01 10:30:40 +01:00
Alexander Neumann
931e6ed2ac Use Seal/Open everywhere 2017-11-01 10:30:40 +01:00
Alexander Neumann
a5f0e9ab65 Remove custom Encrypt/Decrypt methods 2017-11-01 10:30:40 +01:00
Alexander Neumann
6fc133ad6a Run tests on Seal/Open 2017-11-01 10:30:40 +01:00
Alexander Neumann
e1b80859f2 Make crypto.Key implement cipher.AEAD 2017-11-01 10:30:39 +01:00
George Armhold
d069ee31b2 GS backend: limit http concurrency in Save(), Stat(), Test(), Remove(), List()
as per discussion in PR #1399
2017-10-31 08:01:43 -04:00
George Armhold
981752ade0 Azure backend: limit http concurrency in Stat(), Test(), Remove()
as per discussion in PR #1399
2017-10-31 07:32:30 -04:00
Alexander Neumann
d01d07fc0a Merge pull request #1400 from armhold/deadlock2
log unexpected errs from b2 ListCurrentObject()
2017-10-30 18:39:33 +01:00
Alexander Neumann
526aaca6f5 Merge pull request #1389 from wojas/fix-modtime-tests-on-apfs
Fix modtime tests on APFS: allow 1us difference
2017-10-29 14:20:40 +01:00
George Armhold
2f8147af59 log unexpected errs from b2 ListCurrentObject()
gh-1385
2017-10-29 08:53:39 -04:00
Konrad Wojas
f3016a9096 Darwin test fix: allow 1μs timestamp difference
On Darwin, allow a 1μs difference in restored timestamps, because
macOS <10.13 cannot restore with nanosecond precision and the current
version of Go (1.9.2) does not yet support the new syscall required
for this. (#1087 #1389)
2017-10-29 20:48:07 +08:00
Alexander Neumann
f854a41ba9 Merge pull request #1399 from armhold/deadlock2
prevent deadlock in List() for B2 when b2.connections=1
2017-10-29 09:26:46 +01:00
Alexander Neumann
ca3cadef5e Merge pull request #1398 from armhold/sftp-readdir
sftp ReadDir: add path to return error messages (gh-1323)
2017-10-29 09:26:42 +01:00
George Armhold
3304b0fcf0 prevent deadlock in List() for B2 when b2.connections=1
This is a fix for the following situation (gh-1188):

List() grabs a semaphore token upon entry, starts a goroutine, and
does not release the token until the routine exits (via a defer).

The goroutine iterates over the results from ListCurrentObjects(),
sending them one at a time to a channel, where they are ultimately
processed by be.Load().

Since be.Load() also needs a token, this will result in deadlock if
b2.connections=1.

This fix changes List() so that the token is only held during the call
to ListCurrentObjects().
2017-10-28 18:46:47 -04:00
George Armhold
d8938e259a sftp ReadDir: add path to return error messages (gh-1323)
fix missing "Close" string in debug log fmt
2017-10-28 14:16:27 -04:00
Alexander Neumann
53a554c89d Merge pull request #1395 from restic/small-crypto-improvements
Small improvements
2017-10-28 12:57:18 +02:00
Alexander Neumann
e71db01230 Improve crypto test 2017-10-28 12:09:33 +02:00
Alexander Neumann
178e946fc7 Rename KDFParams -> Params 2017-10-28 10:28:29 +02:00
Alexander Neumann
f3bff12939 Merge pull request #1394 from zcalusic/master
Use lowercase in messages from check/tag commands, too
2017-10-28 09:56:29 +02:00
Alexander Neumann
7a99418dc5 Merge pull request #1393 from armhold/lint-errcheck
detect errors from fs.Walk() in local backend List()
2017-10-28 09:56:11 +02:00
Alexander Neumann
c71ba466ea Merge pull request #1391 from armhold/b2-listmax
pass in defaultListMaxItems to b2Backend constructor
2017-10-28 09:54:57 +02:00
Alexander Neumann
8ce5d35543 Merge pull request #1390 from armhold/lint
small cleanup
2017-10-28 09:54:35 +02:00
Zlatko Čalušić
134f834c60 Use lowercase in messages from check/tag commands, too 2017-10-27 21:06:34 +02:00
George Armhold
8a37c07295 send errors from fs.Walk() to debug log
clarify non-err returns from Walk where err is already proved to be nil
2017-10-27 08:41:17 -04:00
George Armhold
bd0ada7842 go fmt 2017-10-26 16:37:11 -04:00
George Armhold
eea96f652d go fmt 2017-10-26 16:22:10 -04:00
George Armhold
38c3061df7 pass in defaultListMaxItems to b2Backend constructor
gh-1385
2017-10-26 14:22:16 -04:00
George Armhold
f5fa602482 detect and return error from file Close() in Node.createFileAt()
gh-1385
2017-10-26 13:53:31 -04:00
Alexander Neumann
e44ac55f63 Merge pull request #1387 from wojas/snapshots-filter-last
Add --last flag to snapshots command
2017-10-26 19:08:49 +02:00
Alexander Neumann
f1cfb73a8b Merge pull request #1384 from wojas/reduce-ticker-refresh
Reduce ticker refresh rate with RESTIC_PROGRESS_FPS env
2017-10-26 19:02:59 +02:00
Konrad Wojas
5b96885c6d Control progress rate with RESTIC_PROGRESS_FPS env
Add a RESTIC_PROGRESS_FPS environment variable to limit the interval
at which the progress indicator updates (allowed values: 1-60).

The default rate of 60 FPS can cause high terminal CPU load on some
systems, like iTerm2 on macOS with font anti-aliasing enabled.

Usage:

    RESTIC_PROGRESS_FPS=1 restic ...
    RESTIC_PROGRESS_FPS=60 restic ...
2017-10-26 14:46:56 +08:00
Konrad Wojas
c5da90a5b7 Add --last flag to snapshots command
Add --last flag to snapshots command to only show the last entry for any
(hostname, paths) combination.

This makes it easier to check when various paths were last backed up.
2017-10-26 14:02:29 +08:00
George Armhold
bcdebfb84e small cleanup:
- be explicit when discarding returned errors from .Close(), etc.
- remove named return values from funcs when naked return not used
- fix some "err" shadowing when redeclaration not needed
2017-10-25 12:03:55 -04:00
Alexander Neumann
359b273649 Merge pull request #1381 from tbm/broken-link
Remove Markdown syntax from documentation
2017-10-24 20:38:40 +02:00
Martin Michlmayr
2e2c8dc620 Remove Markdown syntax from documentation
Convert a link from Markdown to RST syntax.  This link must
have been missed during the recent conversion.
2017-10-24 14:40:33 +01:00
Alexander Neumann
8d37b723ca Upadte vendored dependencies 2017-10-22 10:07:36 +02:00
Alexander Neumann
315b7f282f Remove explicit version constraints 2017-10-22 10:07:23 +02:00
Alexander Neumann
a3f8e9dfa7 Add entry to CHANGELOG 2017-10-21 12:22:50 +02:00
Alexander Neumann
982810f7cc Merge pull request #1368 from TobyLL/master
Ignore comments (lines starting with #) in the --files-from file
2017-10-21 12:21:11 +02:00
Alexander Neumann
90b96d19cd Merge pull request #1365 from felix9/fix_1068
Fix failure to detect some legacy s3 repos
2017-10-21 12:19:58 +02:00
Alexander Neumann
6a52bb6f54 Merge pull request #1374 from restic/update-blazer
Update github.com/kurin/blazer
2017-10-21 11:26:49 +02:00
Alexander Neumann
cacaa4393f Merge pull request #1373 from restic/check-gopkg-lock
CI: Check the vendor dir and Gopkg.lock
2017-10-21 11:14:30 +02:00
Alexander Neumann
d63ab4e9a4 Merge pull request #1358 from prattmic/chunk_size
gs: add option to set chunk size
2017-10-21 11:13:48 +02:00
Alexander Neumann
ca6daec8dd CI: Check the vendor dir and Gopkg.lock 2017-10-21 10:50:38 +02:00
Alexander Neumann
c87f2420a6 Update github.com/kurin/blazer 2017-10-21 10:30:39 +02:00
Alexander Neumann
f5bbbc52f4 Update issue template 2017-10-21 10:17:58 +02:00
TobyLL
9e3dde8ec7 Fix whitespace 2017-10-19 15:52:06 +01:00
TobyLL
9dba182e51 Ignore comments (lines starting with #) in the --files-from file 2017-10-19 15:48:22 +01:00
Felix Lee
944fc857eb Fix failure to detect some legacy s3 repos
Sometimes s3 listobjects for a directory includes an entry for that
directory. The restic s3 backend doesn't expect that and returns
an error.

Symptom is:
  ReadDir: invalid key name restic/key/, removing prefix
     restic/key/ yielded empty string

I'm not sure when s3 does that; I'm unable to reproduce it myself.

But in any case, it seems correct to ignore that when it happens.

Fixes #1068
2017-10-18 13:45:31 -07:00
Alexander Neumann
7507a658ac Merge pull request #1362 from restic/fix-backend-retry
Fix Save() backend retry
2017-10-18 07:45:56 +02:00
Michael Pratt
9fa4f5eb6b gs: disable resumable uploads
By default, the GCS Go packages have an internal "chunk size" of 8MB,
used for blob uploads.

Media().Do() will buffer a full 8MB from the io.Reader (or less if EOF
is reached) then write that full 8MB to the network all at once.

This behavior does not play nicely with --limit-upload, which only
limits the Reader passed to Media. While the long-term average upload
rate will be correctly limited, the actual network bandwidth will be
very spikey.

e.g., if an 8MB/s connection is limited to 1MB/s, Media().Do() will
spend 8s reading from the rate-limited reader (performing no network
requests), then 1s writing to the network at 8MB/s.

This is bad for network connections hurt by full-speed uploads,
particularly when writing 8MB will take several seconds.

Disable resumable uploads entirely by setting the chunk size to zero.
This causes the io.Reader to be passed further down the request stack,
where there is less (but still some) buffering.

My connection is around 1.5MB/s up, with nominal ~15ms ping times to
8.8.8.8.

Without this change, --limit-upload 1024 results in several seconds of
~200ms ping times (uploading), followed by several seconds of ~15ms ping
times (reading from rate-limited reader). A bandwidth monitor reports
this as several seconds of ~1.5MB/s followed by several seconds of
0.0MB/s.

With this change, --limit-upload 1024 results in ~20ms ping times and
the bandwidth monitor reports a constant ~1MB/s.

I've elected to make this change unconditional of --limit-upload because
the resumable uploads shouldn't be providing much benefit anyways, as
restic already uploads mostly small blobs and already has a retry
mechanism.

--limit-download is not affected by this problem, as Get().Download()
returns the real http.Response.Body without any internal buffering.

Updates #1216
2017-10-17 21:12:04 -07:00
Alexander Neumann
ce4d71d626 backend: Add partial read failure to error backend 2017-10-17 22:11:38 +02:00
Alexander Neumann
8e2ef3f38b cache: Store first, then store in the backend
Store the file in the backend, then rewind the reader and store it
in the cache afterwards.
2017-10-17 22:01:20 +02:00
Alexander Neumann
8dc952775e backend: Correctly retry Save() calls
Make sure the given reader is an io.Seeker and rewind it properly each
time.
2017-10-17 21:46:38 +02:00
Alexander Neumann
99b6163e27 Add 'dump' to manual 2017-10-16 20:24:47 +02:00
Alexander Neumann
beaf55f1fc dump: Allow absolute paths 2017-10-16 20:22:01 +02:00
Alexander Neumann
980bb9059f Add entry to changelog 2017-10-16 20:15:19 +02:00
Alexander Neumann
0e7281eb71 Merge pull request #1346 from fawick/cmdCatFile
Add dump sub-command for dumping files to stdout
2017-10-16 20:13:44 +02:00
Alexander Neumann
0b6133d7b5 Merge pull request #1360 from mungomat/fuse_bugfix
fuse: bugfix: ignore empty tags
2017-10-16 20:07:05 +02:00
Tobias Klein
b57ca64275 fuse: bugfix: ignore empty tags 2017-10-16 16:22:08 +02:00
Alexander Neumann
faadbd734b Add entry to CHANGELOG 2017-10-14 20:29:47 +02:00
Alexander Neumann
88b0a93409 Merge pull request #1353 from restic/backend-retry
Retry failed backend requests
2017-10-14 20:28:57 +02:00
Alexander Neumann
4a995105a9 sftp: Fix Delete() 2017-10-14 16:08:15 +02:00
Alexander Neumann
7fe496f983 Ensure TestDelete runs last 2017-10-14 16:04:29 +02:00
Alexander Neumann
e56370eb5b Remove Deleter interface 2017-10-14 16:04:29 +02:00
Alexander Neumann
b8af7f63a0 backend test: Always remove files for TestList 2017-10-14 15:56:25 +02:00
Alexander Neumann
3eea555155 Add dependency cenkalti/backoff 2017-10-14 15:56:25 +02:00
Alexander Neumann
897c923cc9 Retry failed backend requests 2017-10-14 15:56:25 +02:00
Alexander Neumann
67193e3deb Improve error message when creating lock failed 2017-10-14 15:56:25 +02:00
Alexander Neumann
0e722efb09 backend: Add Delete() to restic.Backend interface 2017-10-14 15:56:25 +02:00
Alexander Neumann
3736f33ebf Merge pull request #1352 from tyll/manual_links
Fix links to manual
2017-10-14 14:54:16 +02:00
Fabian Wickborn
d1d9c3f9d7 Renamed cmd_catfile.go to cmd_dump.go 2017-10-14 13:55:21 +02:00
Fabian Wickborn
cd5cbe0910 Rename debug dump related variable and run function 2017-10-14 13:55:00 +02:00
Fabian Wickborn
814e992c0b Rename subcommand catfile to dump 2017-10-14 11:34:04 +02:00
Till Maas
660fe78735 Fix links to manual 2017-10-13 20:01:19 +02:00
Fabian Wickborn
87d084e18c Add subcommand dump 2017-10-12 20:24:34 +02:00
Alexander Neumann
9ce2a73fc5 Add issue and PR to the changelog entry 2017-10-12 20:20:06 +02:00
Alexander Neumann
f2314b26ba Move 'dump' to 'debug dump' 2017-10-12 20:18:45 +02:00
Alexander Neumann
74dcf41f25 Disable 'dump' for non-debug builds 2017-10-12 20:14:48 +02:00
Alexander Neumann
b6ba30186f Merge pull request #1336 from rmdashrf/rate-limit-backup
Add basic rate limiting to backup
2017-10-12 20:08:55 +02:00
rmdashrf
32637a0328 Basic rate limiting implementation.
Added `--limit-upload` and `--limit-download` flags to rate limit
backups and restores.
2017-10-11 20:01:20 -07:00
Alexander Neumann
0addd90e14 Merge pull request #1345 from harshavardhana/creds
Refactor credentials management to support multiple mechanisms.
2017-10-11 21:01:19 +02:00
Alexander Neumann
1b5ee5b10a Merge pull request #1343 from mungomat/fuse_speedup
fuse mount: speedup
2017-10-11 20:55:52 +02:00
Harshavardhana
042adeb5d0 Refactor credentials management to support multiple mechanisms.
This PR adds the ability of chaining the credentials provider,
such that restic as a tool attempts to honor credentials from
multiple different ways.

Currently supported mechanisms are

 - static (user-provided)
 - IAM profile (only valid inside configured ec2 instances)
 - Standard AWS envs (AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY)
 - Standard Minio envs (MINIO_ACCESS_KEY, MINIO_SECRET_KEY)

Refer https://github.com/restic/restic/issues/1341
2017-10-09 12:51:39 -07:00
Tobias Klein
7e4ce0dacc fuse mount: speedup 2017-10-09 10:26:56 +02:00
Alexander Neumann
8ceb22fe8a Add entry to CHANGELOG 2017-10-08 09:48:22 +02:00
Alexander Neumann
c5553ec855 Merge pull request #1276 from fawick/supply_ca_cert
Add REST backend option to use CA root certificate
2017-10-08 09:47:23 +02:00
Alexander Neumann
bb3ed54291 Merge pull request #1337 from mungomat/fuse_updateRepo
fuse: mount and backup in parallel (#1330)
2017-10-08 09:45:28 +02:00
Alexander Neumann
513ba3b6f7 Merge pull request #1340 from jniggemann/fix-664
doc: Add info on docker container
2017-10-08 09:44:34 +02:00
Alexander Neumann
17d688afef Merge pull request #1339 from felix9/fix_1251
fixes #1251, race when writing indexes
2017-10-07 16:36:16 +02:00
Jan Niggemann
d81eee26b3 doc: Add info on docker container
fixes #664
2017-10-07 16:03:08 +02:00
Felix Lee
cc5ada63a4 fixes #1251, race when writing indexes 2017-10-07 05:11:42 -07:00
Alexander Neumann
88fb60e0b5 Improve issue template 2017-10-07 12:16:46 +02:00
Tobias Klein
02200acad0 fuse: mount and backup in parallel (#1330) 2017-10-07 11:48:48 +02:00
Alexander Neumann
1a2d190bdb Merge pull request #1334 from felix9/backup_tags
backup should use latest parent regardless of tags
2017-10-07 10:45:02 +02:00
Alexander Neumann
2db4ff168a Merge pull request #1327 from fawick/fix-1294
Create missing lock dir when saving lock
2017-10-07 10:40:29 +02:00
Alexander Neumann
a77c8cc5d2 Add entry to CHANGELOG 2017-10-07 10:23:56 +02:00
Alexander Neumann
b8866c1fe4 Merge pull request #1326 from fawick/rejectionCache
Cache evaluated directories in isExcludedByFile
2017-10-07 09:57:54 +02:00
Fabian Wickborn
f0f17db847 Cache evaluated directories in isExcludedByFile
Fixes #1271.
2017-10-06 22:11:22 +02:00
Alexander Neumann
a5c003acb0 Merge pull request #1333 from restic/fix-1328
Do not cache invalid/truncated files
2017-10-05 21:36:52 +02:00
Felix Lee
7b44fd0f9d fix #1143
Backup was choosing a parent snapshot that had the same tags, which
makes backup unnecessarily slow when there are newer snapshots with
different tags.

There's no reason parent has to have the same tags.

This change makes backup choose the newest snapshot instead.
2017-10-05 11:48:26 -07:00
Alexander Neumann
cebee0b8fa cache: Refuse to cache truncated files 2017-10-05 20:40:02 +02:00
Alexander Neumann
d886bc6c48 Ignore invalid index files, print warning 2017-10-05 20:39:53 +02:00
Alexander Neumann
d81adcfaa5 cache: Add file name to error message 2017-10-05 19:30:56 +02:00
Fabian Wickborn
6da9bfbbce Create missing lock dir when saving lock 2017-10-05 00:07:48 +02:00
Fabian Wickborn
69a6e622d0 Add REST backend option to use CA root certificate
Closes #1114.
2017-10-04 22:14:10 +02:00
Alexander Neumann
1dcfd64028 Merge pull request #1325 from antonlindstrom/lookup-group
Add group name in fillUser
2017-10-04 21:32:46 +02:00
Alexander Neumann
5d1c1f721e Merge pull request #1324 from antonlindstrom/password-feedback
Add password successful feedback
2017-10-04 21:31:24 +02:00
Alexander Neumann
fbc8bbf305 Merge pull request #1321 from damienstanton/master
fix broken installation link in README
2017-10-04 21:02:33 +02:00
Alexander Neumann
cdef55bb88 Merge pull request #1312 from hgfischer/issue-1119
nit: please remove dot imports #1119
2017-10-04 21:01:40 +02:00
Anton Lindstrom
26df48b2aa Add group name in fillUser
This does a lookup of the group name from the GID and adds it to the
Node.
2017-10-04 20:11:54 +02:00
Anton Lindstrom
a7baea0522 Output password successful on terminal stdout
This removes the conditions that checks if the password is supplied
through environment variable or file and outputs password is successful
on terminal and when --quiet is not supplied.
2017-10-04 14:55:04 +02:00
Anton Lindstrom
55e6003749 Add password successful feedback
This adds some feedback when entering the password on the command line.
When the password is entered and supplied through stdin (and stdout is a
terminal) then the a message saying `password is correct` if correct is
printed.
2017-10-04 13:45:05 +02:00
damienstanton
846acd5d4c use https 2017-10-03 14:28:17 -04:00
Herbert
43f8145858 Revert "Add .vscode to gitignore"
This reverts commit 1b5242b4f9.
2017-10-03 20:21:54 +02:00
damienstanton
79759928f6 fix broken installation link in README 2017-10-03 13:28:33 -04:00
Alexander Neumann
eb59d28154 Add entry to CHANGELOG 2017-10-03 18:54:07 +02:00
Alexander Neumann
79f63a2e74 Merge pull request #1320 from restic/add-snapshots-json-short-id
snapshots: Add short ID to JSON output
2017-10-03 18:53:17 +02:00
Alexander Neumann
6a62254048 Merge pull request #1299 from mungomat/fuse_addSnapshotIDs
fuse: Add a snapshot IDs directory (#1102)
2017-10-03 18:53:05 +02:00
Alexander Neumann
657a1d75af snapshots: Add short ID to JSON output
We're using the short ID in all output to users, so it should also be
included in the JSON output of the `snapshots` command.
2017-10-03 15:24:09 +02:00
Alexander Neumann
2694def56a Add entry to CHANGELOG 2017-10-03 14:52:10 +02:00
Alexander Neumann
7843341da3 Merge pull request #1319 from antonlindstrom/check-ok-output
Add explicit OK output to check command
2017-10-03 14:51:31 +02:00
Anton Lindstrom
d46314648e Add explicit OK output to check command
This adds additional output to the check command when no errors were
found. It means that when all checks have been completed, the following
output is displayed:

	No errors were found

The output is added to make sure that it is easier to understand that no
errors were found.

Full example output:

	Create exclusive lock for repository
	Load indexes
	Check all packs
	Check snapshots, trees and blobs
	No errors were found
2017-10-03 12:57:51 +02:00
Alexander Neumann
e45011af57 Merge pull request #1304 from jannickfahlbusch/showAmountOfSnapshots
Show the number of snapshots
2017-10-03 12:41:47 +02:00
Alexander Neumann
fb09884893 Merge pull request #1311 from felix9/dump-no-trees
"dump" command no longer has a "trees" option
2017-10-03 12:40:24 +02:00
Alexander Neumann
d1eecafa63 Merge pull request #1314 from jannickfahlbusch/fixTypo
Fix typo in comment
2017-10-03 12:39:24 +02:00
Alexander Neumann
b47d991f56 Merge pull request #1316 from restic/prune-wrong-data
prune: Warn about wrong plaintext blob ID
2017-10-03 12:39:04 +02:00
Alexander Neumann
553ea812a7 Add entry to CHANGELOG 2017-10-03 12:38:00 +02:00
Alexander Neumann
216e374310 Merge pull request #1315 from prattmic/cache_doc
Clarify cache location documentation
2017-10-03 12:36:53 +02:00
Alexander Neumann
034b0b8040 forget: Run prune for manually forgotten snapshots 2017-10-03 11:56:13 +02:00
Michael Pratt
5ab9e12b46 Clarify cache location documentation
PR #1287 changed the default cache location on darwin and windows.
Update the changelog and manual to reflect the new behavior.

Since the cache hasn't been included in an official release yet, I've
just changed the main cache changelog entry.

Fixes #1309
2017-10-03 11:44:09 +02:00
Alexander Neumann
abe6e0d22d Merge pull request #1300 from jniggemann/refactor-manual
doc: Refactors the documentation
2017-10-03 11:41:00 +02:00
Jan Niggemann
f5b550191c doc: Refactors the documentation
This commit refactors the documentation according to my proposal in #1273
and the discussion I had with fd0 on IRC.

The bits from the manual that I could not immediately put into the new
structure are contained in manual_rest.rst Anything else is still there,
nothing has been deleted.

I changed the heading markup to follow the convention used in Python’s
Style Guide for documentation, this convention is explained in a comment
at the top of every file.

I also added a paragraph on installing restic on Debian.
2017-10-03 11:21:53 +02:00
Jannick Fahlbusch
3dcacb3730 Fix typo in comment 2017-10-02 18:19:22 +02:00
Herbert
033589a66b Use rtest on these as well to keep codebase consistent 2017-10-02 17:48:08 +02:00
Jannick Fahlbusch
e46a647c45 Just print the number of snapshots 2017-10-02 17:05:59 +02:00
Alexander Neumann
f26492fc2d prune: Warn about wrong plaintext blob ID 2017-10-02 16:27:08 +02:00
Herbert
3473c3f7b6 Remove all dot-imports 2017-10-02 15:06:39 +02:00
Herbert
1b5242b4f9 Add .vscode to gitignore 2017-10-02 15:06:23 +02:00
Felix Lee
6d897def1b also fix man page 2017-10-01 18:33:41 -07:00
Jannick Fahlbusch
2f10e25738 Show the amount of snapshots 2017-10-01 17:14:54 +02:00
Felix Lee
555bd257bd dump command no longer has 'trees' option 2017-10-01 08:04:52 -07:00
Alexander Neumann
3afd974dea Add entry to CHANGELOG 2017-10-01 10:35:15 +02:00
Alexander Neumann
f4120c9d45 Merge pull request #1301 from restic/update-blazer
Update vendored dependencies
2017-10-01 10:33:54 +02:00
Alexander Neumann
61cb1cc6f8 Update vendored dependencies
This includes github.com/kurin/blazer 0.2.0, which resolves #1291
2017-10-01 10:13:39 +02:00
Tobias Klein
49bc1d0b3b fuse: Add a snapshot IDs directory (#1102) 2017-09-30 16:22:14 +02:00
Alexander Neumann
ba23d24dd1 Merge pull request #1298 from restic/fix-1288
sftp: Handle/Document tilde character
2017-09-30 12:28:09 +02:00
Alexander Neumann
556a63de19 sftp: Return error when path starts with a tilde (~) 2017-09-30 10:34:23 +02:00
Alexander Neumann
fae3c4d437 faq: Add entry about Synology NAS' sftp path 2017-09-30 10:30:21 +02:00
Alexander Neumann
89c2ed2a1c manual: Document that sftp does not expand tilde 2017-09-30 10:24:28 +02:00
Alexander Neumann
23f1cb06d6 Fix FAQ 2017-09-30 10:21:56 +02:00
Alexander Neumann
ac92e2dd2d Add to CHANGELOG entry 2017-09-29 21:45:37 +02:00
Alexander Neumann
bf58425351 Merge pull request #1287 from prattmic/cachedir
cache: OS-specific cache directories
2017-09-29 21:42:34 +02:00
Alexander Neumann
a3dc0ab398 Merge pull request #1295 from bachp/patch-2
Fix wrong quotes for command in installation.
2017-09-29 21:41:13 +02:00
Pascal Bach
224ebdb8b9 Fix wrong quotes for command in installation. 2017-09-29 10:08:24 +02:00
Alexander Neumann
cf80d295f3 Merge pull request #1285 from bachp/patch-1
Add instruction on how to install restic using Nix
2017-09-28 22:03:19 +02:00
Michael Pratt
2133869127 cache: OS-specific cache directories
Windows, and to a lesser extent OS X, don't conform to XDG and have
their own preferred locations for caches.

On Windows, use %LOCALAPPDATA%/restic (i.e., ~/AppData/Local/restic). I
can't find authoritative documentation from Microsoft recommending
specifically which of %APPDATA%, %LOCALAPPDATA%, and %TEMP% should be
used for caches, but %LOCALAPPDATA% is where browsers store their
caches, so it seems like a good fit.

On OS X, use ~/Library/Caches/restic, which is recommended by the Apple
documentation. They do suggest using the application "bundle identifier"
as the base folder name, but restic doesn't have one, so I just used
"restic".
2017-09-27 21:16:22 -07:00
Pascal Bach
97330ac621 Add instruction on how to install restic using Nix 2017-09-27 22:41:20 +02:00
Alexander Neumann
1ee1559506 Add release script 2017-09-26 22:14:58 +02:00
Alexander Neumann
eccc336319 Add entry to CHANGELOG 2017-09-26 14:18:37 +02:00
Alexander Neumann
7fe657ec71 Merge pull request #1282 from restic/rework-autogeneration
Rework generation of manpages and completion files
2017-09-26 14:16:41 +02:00
Alexander Neumann
77c07bfd19 Remove integration test for manpage 2017-09-26 13:16:55 +02:00
Alexander Neumann
4de938d97a Update manpages and auto-completion 2017-09-26 13:16:08 +02:00
Alexander Neumann
dad1c87afe Rework generation of manpages and completion files
This commit removes the `manpages` and `autocomplet` commands and
replaces them with the more generic `generate` command. Also, zsh
completion file support was added.
2017-09-26 13:12:12 +02:00
Alexander Neumann
801dbb6d03 Merge pull request #1281 from prattmic/gcs_perms
gs: allow backend creation without storage.buckets.get
2017-09-26 09:38:33 +02:00
Michael Pratt
fa0be82da8 gs: allow backend creation without storage.buckets.get
If the service account used with restic does not have the
storage.buckets.get permission (in the "Storage Admin" role), Create
cannot use Get to determine if the bucket is accessible.

Rather than always trying to create the bucket on Get error, gracefully
fall back to assuming the bucket is accessible. If it is, restic init
will complete successfully. If it is not, it will fail on a later call.

Here is what init looks like now in different cases.

Service account without "Storage Admin":

Bucket exists and is accessible (this is the case that didn't work
before):

$ ./restic init -r gs:this-bucket-does-exist:/
enter password for new backend:
enter password again:
created restic backend c02e2edb67 at gs:this-bucket-does-exist:/

Please note that knowledge of your password is required to access
the repository. Losing your password means that your data is
irrecoverably lost.

Bucket exists but is not accessible:

$ ./restic init -r gs:this-bucket-does-exist:/
enter password for new backend:
enter password again:
create key in backend at gs:this-bucket-does-exist:/ failed:
service.Objects.Insert: googleapi: Error 403:
my-service-account@myproject.iam.gserviceaccount.com does not have
storage.objects.create access to object this-bucket-exists/keys/0fa714e695c8ecd58cb467cdeb04d36f3b710f883496a90f23cae0315daf0b93., forbidden

Bucket does not exist:

$ ./restic init -r gs:this-bucket-does-not-exist:/
create backend at gs:this-bucket-does-not-exist:/ failed:
service.Buckets.Insert: googleapi: Error 403:
my-service-account@myproject.iam.gserviceaccount.com does not have storage.buckets.create access to bucket this-bucket-does-not-exist., forbidden

Service account with "Storage Admin":

Bucket exists and is accessible: Same

Bucket exists but is not accessible: Same. Previously this would fail
when Create tried to create the bucket. Now it fails when trying to
create the keys.

Bucket does not exist:

$ ./restic init -r gs:this-bucket-does-not-exist:/
enter password for new backend:
enter password again:
created restic backend c3c48b481d at gs:this-bucket-does-not-exist:/

Please note that knowledge of your password is required to access
the repository. Losing your password means that your data is
irrecoverably lost.
2017-09-25 22:25:51 -07:00
Alexander Neumann
7e8bc8d362 Merge pull request #1279 from restic/fix-eof-error
cache: Synchronize downloading
2017-09-25 16:39:20 +02:00
Alexander Neumann
0bb2a8e0d0 cache: Synchronize downloading
This commit adds code to synchronize downloading files to the cache.
Before, requests that came in for files currently downloading would fail
because the file was not completed in the cache. Now, the code waits
until the download is completed.

Closes #1278
2017-09-25 15:58:20 +02:00
Alexander Neumann
2e72b57f2f Correct debug message 2017-09-25 14:35:37 +02:00
Alexander Neumann
bff1039e3a Add entry to CHANGELOG 2017-09-25 13:17:44 +02:00
Alexander Neumann
5a999cb77f Merge pull request #1040 from restic/add-cache
Add local cache
2017-09-25 13:13:07 +02:00
Alexander Neumann
3a2539e0ac doc: Update manpages 2017-09-24 23:13:04 +02:00
Alexander Neumann
e262f35d0a cache: Auto-remove invalid files 2017-09-24 23:11:47 +02:00
Alexander Neumann
176bfa6529 backend: Improve ReadAt 2017-09-24 23:11:23 +02:00
Alexander Neumann
240c4cf2fd cache: In case of an error, fall back backend 2017-09-24 23:11:23 +02:00
Alexander Neumann
db5ec5d876 repo: Automatically cache tree-only pack files 2017-09-24 23:11:23 +02:00
Alexander Neumann
e1dfaf5d87 cache: Allow proactive caching of tree packs
This commit adds a function to the cache which can decide to proactively
load the complete pack file and store it in the cache. This is helpful
for pack files containing only tree blobs, as it is likely that the same
file is accessed again in the future.
2017-09-24 23:11:23 +02:00
Alexander Neumann
5436154f0d cache: Add PerformReadahead 2017-09-24 23:11:23 +02:00
Alexander Neumann
809e218d20 cache: Improve debug logs 2017-09-24 23:11:23 +02:00
Alexander Neumann
1eaad6cebb index: Add TreePacks() 2017-09-24 21:54:53 +02:00
Alexander Neumann
56fccecd06 prune: Repack mixed pack files 2017-09-24 21:54:53 +02:00
Alexander Neumann
3890a947ca Clear data files in cache 2017-09-24 21:54:53 +02:00
Alexander Neumann
e299272378 repo: Try cached pack files first 2017-09-24 21:54:53 +02:00
Alexander Neumann
70248bd05a repo: Clear indexes 2017-09-24 21:54:53 +02:00
Alexander Neumann
7a5fde8f5a repository: Save pack files for trees in cache 2017-09-24 21:54:53 +02:00
Alexander Neumann
62ba9f1950 check: Disable cache by default 2017-09-24 21:54:53 +02:00
Alexander Neumann
610b676444 Automatically exclude current restic cache 2017-09-24 21:54:53 +02:00
Alexander Neumann
58699e3c90 Write CACHEDIR.TAG to cache base directory 2017-09-24 21:54:53 +02:00
Alexander Neumann
9be24a1c9f Add cache
This commits adds rudimentary support for a cache directory, enabled by
default. The cache directory is created if it does not exist. The cache
is used if there's anything in it, newly created snapshot and index
files are written to the cache automatically.
2017-09-24 21:54:53 +02:00
Alexander Neumann
5ace41471e Merge pull request #1277 from prattmic/gcs_cleanup
Document GCS permission requirements
2017-09-24 20:49:02 +02:00
Michael Pratt
3b2106ed30 gs: document required permissions
In the manual, state which standard roles the service account must
have to work correctly, as well as the specific permissions required,
for creating even more specific custom roles.
2017-09-24 11:25:57 -07:00
Michael Pratt
5f4f997126 gs: minor comment cleanups
* Remove a reference to S3.
* Config can only be used for GCS, not other "gcs compatibile servers".
* Make comments complete sentences.
2017-09-24 10:10:56 -07:00
Alexander Neumann
49d397a419 Merge pull request #1275 from fawick/sort_snapshots
Always sort snapshots lists ascending by timestamp
2017-09-24 16:32:40 +02:00
Fabian Wickborn
ea1ab96749 Always sort snapshots lists ascending by timestamp
Fixes #1219.
2017-09-24 13:01:13 +02:00
Alexander Neumann
24c62e719a Add entry to CHANGELOG 2017-09-23 22:15:10 +02:00
Alexander Neumann
9c6b7f688e Merge pull request #1270 from restic/sftp-allow-password-prompt
sftp: Allow password entry
2017-09-23 22:13:04 +02:00
Alexander Neumann
d41dce5ecb Merge pull request #1272 from jniggemann/doc-faq-add-prio
doc: FAQ: Add info on IO and CPU prioritization
2017-09-23 20:08:17 +02:00
Jan Niggemann
52a3eafede doc: FAQ: Add info on IO and CPU prioritization 2017-09-23 19:32:07 +02:00
Alexander Neumann
55dfc85159 manual: Add hint for RESTIC_PASSWORD_FILE 2017-09-23 19:16:07 +02:00
Alexander Neumann
a7a478a19e doc: Correct FAQ 2017-09-23 19:15:21 +02:00
Alexander Neumann
2080afd9de Merge pull request #1259 from jniggemann/doc-add-restic-check-advice
adds advice to run restic check regularly
2017-09-23 14:07:55 +02:00
Alexander Neumann
9aa136b982 Merge pull request #1260 from jniggemann/doc-add-info-on-special-items
Doc add info on special items
2017-09-23 14:07:20 +02:00
Alexander Neumann
3a191f37cb Add entry to CHANGELOG 2017-09-23 14:05:55 +02:00
Alexander Neumann
429106340f Merge pull request #1267 from harshavardhana/possible-fix-memory
Implement Size() and Len() to know the optimal size.
2017-09-23 14:04:15 +02:00
Alexander Neumann
530c73b457 Merge pull request #1269 from mrzv/forget-compact
Add --compact option to forget
2017-09-23 14:02:34 +02:00
Alexander Neumann
fb9729fdb9 sftp: Allow password entry
This was a bit tricky: We start the ssh binary, but we want it to ignore
SIGINT. In contrast, restic itself should process SIGINT and clean up
properly. Before, we used `setsid()` to give the ssh process its own
process group, but that means it cannot prompt the user for a password
because the tty is gone.

So, now we're passing in two functions that ignore SIGINT just before
the ssh process is started and re-install it after start.
2017-09-23 11:43:33 +02:00
Alexander Neumann
45a09c76ff Allow suspending SIGINT handler 2017-09-23 11:12:56 +02:00
Dmitriy Morozov
efd65a1b65 Update manpage for forget 2017-09-22 16:35:58 -07:00
Dmitriy Morozov
ae60188eb9 Add --compact option to forget 2017-09-22 16:32:59 -07:00
Jan Niggemann
3b904525d9 manual: Add info on special items (device files) 2017-09-22 22:58:26 +02:00
Jan Niggemann
1e31f5202f manual: Add info on special items
Add info about handling of symlinks and bind-mounts.

Closes: #1014
2017-09-22 22:51:31 +02:00
Jan Niggemann
f12d41138a Add advice to run check regularly 2017-09-22 22:27:10 +02:00
Harshavardhana
98369f6a5d Implement Size() and Len() to know the optimal size. 2017-09-22 12:09:17 -07:00
Alexander Neumann
8f9bf1995b Merge pull request #1265 from restic/improve-packers
Improve packers, prepare for cache
2017-09-22 16:16:10 +02:00
Alexander Neumann
e7de3b5f9d Merge pull request #1266 from JaCoB1123/document_hostname_option
Add note about rescan to hostname flag (fixes #1221)
2017-09-22 15:40:24 +02:00
Alexander Neumann
3541d06d07 repo: Split packers for tree and data
The code now bundles tree blobs and data blobs into different pack
files, so we'll end up with pack files that either only contain data or
trees. This is in preparation to adding a cache (#1040), because
tree-only pack files can easily be cached later on.
2017-09-22 15:36:47 +02:00
Alexander Neumann
db0e3cd772 repo: Remove packer limits
This commit simplifies finding a packer: The first open packer is taken,
and the upper limit for the pack file is removed.
2017-09-22 15:36:47 +02:00
Alexander Neumann
d3fee08f9a Merge pull request #1263 from restic/cleanups
Small cleanups
2017-09-22 15:36:13 +02:00
Jan Bader
727fb6eabe Add note about rescan to hostname flag (fixes #1221) 2017-09-22 14:29:04 +02:00
Alexander Neumann
d610c60991 repo: Remove unused sync.Pool 2017-09-22 12:37:10 +02:00
Alexander Neumann
3f6e11d26e Allow sorting nodes in trees 2017-09-22 12:37:05 +02:00
Alexander Neumann
a4577769ae Merge pull request #1262 from mafgh/bytes
fix duplicate bytes in prune output
2017-09-22 11:59:10 +02:00
Stefan Völkel
7f927d4774 fix duplicate bytes in prune output 2017-09-22 10:07:24 +02:00
Alexander Neumann
e091673f8f Merge pull request #1258 from restic/sftp-remove-stat
local/sftp: Remove unneeded stat() call
2017-09-21 22:37:12 +02:00
Alexander Neumann
9842eff887 local/sftp: Remove unneeded stat() call 2017-09-21 21:47:03 +02:00
Alexander Neumann
c40b3d3983 Add entry to CHANGELOG 2017-09-21 20:34:17 +02:00
Alexander Neumann
ac5eefdee4 Merge pull request #1249 from mungomat/fuse_symlink
fuse: added symlink 'latest' to snapshots-dir
2017-09-21 20:33:10 +02:00
Alexander Neumann
bf508643a5 Merge pull request #1257 from restic/update-deps
Update vendored dependencies
2017-09-21 20:19:25 +02:00
Alexander Neumann
02fc16e97d Update vendored dependencies 2017-09-21 17:48:45 +02:00
Tobias Klein
1a83a739dc fuse: added symlink 'latest' to snapshots-dir 2017-09-21 16:41:20 +02:00
Alexander Neumann
81473f4538 Add VERSION file for 0.7.3 2017-09-20 20:50:07 +02:00
Alexander Neumann
e1a847e4d1 Add new version to CHANGELOG 2017-09-20 20:49:55 +02:00
Alexander Neumann
0f426c3795 Merge pull request #1254 from jniggemann/doc_metadata
documents metadata handling, fixes #647
2017-09-20 20:24:57 +02:00
Alexander Neumann
6df3d169b8 Add entry to CHANGELOG 2017-09-20 11:05:35 +02:00
Alexander Neumann
5479daa6d4 Merge pull request #1247 from restic/fix-1246
Fix backend List()
2017-09-20 11:04:05 +02:00
Jan Niggemann
397fec0152 documents metadata handling, fixes #647 2017-09-19 15:49:54 +02:00
Alexander Neumann
d7e644272f prune: Add plausibility check 2017-09-19 10:50:07 +02:00
Alexander Neumann
e91749bbb0 Merge pull request #1245 from anarcat/faq
add explanation of restic automation
2017-09-19 10:46:55 +02:00
Antoine Beaupré
bcd1e45ba7 fix typo, add note about file permissions 2017-09-18 08:55:18 -04:00
Alexander Neumann
4c6b626db6 backend: Improve TestList 2017-09-18 13:18:42 +02:00
Alexander Neumann
835ba16c27 b2: Add pagination for List() 2017-09-18 12:13:35 +02:00
Alexander Neumann
3b6a580b32 backend: Make pagination for List configurable 2017-09-18 12:01:54 +02:00
Alexander Neumann
01c486d486 Merge pull request #1250 from dvrkps/patch-1
travis: update go versions
2017-09-17 20:59:35 +02:00
Alexander Neumann
6342a08a16 Merge pull request #1248 from mungomat/fuse_typo
fuse: typo
2017-09-17 20:57:42 +02:00
Davor Kapsa
94c8ee11f8 travis: update go versions 2017-09-17 19:02:22 +02:00
Tobias Klein
9b38980ed9 fuse: typo 2017-09-17 17:39:28 +02:00
Alexander Neumann
649c536250 backend: Improve test for pagination in list 2017-09-17 11:36:45 +02:00
Alexander Neumann
dd49e2b12d Azure: Fix List(), use pagination marker 2017-09-17 11:32:05 +02:00
Alexander Neumann
f61dab1774 backend: Add test for List() 2017-09-17 11:09:16 +02:00
Alexander Neumann
40edf00182 gs: implement pagination 2017-09-17 11:08:51 +02:00
Alexander Neumann
c35518a865 Azure/GS: Remove ReadDir() 2017-09-17 11:05:30 +02:00
Antoine Beaupré
7a0b4428e3 add explanation of restic automation
every time i look at restic, i block on this and figured it may be useful for others
2017-09-16 10:17:36 -04:00
Alexander Neumann
c784a15aaa Merge pull request #1244 from restic/fix-swift-backend-tests
Ignore "not exist" errors for swift backend tests
2017-09-16 14:54:32 +02:00
Alexander Neumann
ce180de9b8 Merge pull request #1243 from restic/improve-error-reporting
Improve error reporting
2017-09-16 14:54:30 +02:00
Alexander Neumann
fca9a523e9 Merge pull request #1241 from restic/fix-timestamp-check
Use .Equal() instead of == for time.Time
2017-09-16 14:54:26 +02:00
Alexander Neumann
8a3889be11 Merge pull request #1240 from restic/config-autocomplete-dir
Correct bash completion file path
2017-09-16 14:54:20 +02:00
Alexander Neumann
2a1633621b Ignore "not exist" errors for swift backend tests 2017-09-16 13:59:55 +02:00
Alexander Neumann
e2deeceb1b Update manpage 2017-09-16 11:29:37 +02:00
Alexander Neumann
d4e994de7b Improve error reporting
This will print the error (including a stack trace) if available before
exiting.
2017-09-16 10:55:13 +02:00
Alexander Neumann
a60e751217 Use .Equal() instead of == for time.Time
Closes #1238
2017-09-15 20:57:35 +02:00
Alexander Neumann
81c5d8a968 Correct bash completion file path 2017-09-15 20:45:16 +02:00
Alexander Neumann
5b1e4df177 Add version to CHANGELOG 2017-09-13 17:14:43 +02:00
Alexander Neumann
4d80744cbb Add VERSION file for 0.7.2 2017-09-13 17:14:26 +02:00
Alexander Neumann
e243d4b7ee Merge pull request #1236 from restic/update-deps
Update dependencies
2017-09-13 14:51:07 +02:00
Alexander Neumann
dce35fcb00 Merge pull request #1232 from prattmic/patch-1
doc: remove broken link
2017-09-13 14:18:46 +02:00
Alexander Neumann
e45a21b0b6 Merge pull request #1231 from restic/fix-local-datadir-create
local: Fix creating data dirs
2017-09-13 14:15:30 +02:00
Alexander Neumann
fda563d606 Update dependencies 2017-09-13 14:09:48 +02:00
Alexander Neumann
f3b49987f8 Add entry to CHANGELOG 2017-09-13 14:04:55 +02:00
Alexander Neumann
c8c01a5cae Merge pull request #1223 from mrzv/snapshots-compact
Add --compact option to snapshots
2017-09-13 14:02:31 +02:00
Michael Pratt
f7ece90129 doc: remove broken link
The link to SFTP is broken, remove it.

I don't even bother to put a new link, since SFTP is literally the next section.
2017-09-12 21:02:34 -07:00
Alexander Neumann
0f25ef9498 Merge pull request #1230 from shayneholmes/update-short-help
Update style in short help commands
2017-09-11 22:25:31 +02:00
Alexander Neumann
5bf2228596 local: Fix creating data dirs 2017-09-11 21:48:25 +02:00
Alexander Neumann
227b01395f local: Add test for open non-existing dir 2017-09-11 21:34:26 +02:00
Shayne Holmes
9f52fe1a10 Update manpages
This is a programmatic change; just ran `restic man-page`
2017-09-11 12:05:51 -07:00
Shayne Holmes
affc6c3390 Correct manpage command 2017-09-11 11:21:44 -07:00
Alexander Neumann
951a34dcbf Add entry to CHANGELOG 2017-09-11 19:02:36 +02:00
Alexander Neumann
36eaa22ed0 Merge pull request #1205 from mungomat/backup_time
Backup time
2017-09-11 19:01:41 +02:00
Alexander Neumann
62df316356 Merge pull request #1194 from mungomat/bugfix_bucker_always
fix: bucker
2017-09-11 19:01:23 +02:00
Shayne Holmes
00797fdd85 Synchronize manual with help output
Two commands in the output weren't mentioned in the manual.
2017-09-11 09:33:19 -07:00
Shayne Holmes
9eb39cef05 Capitalize short help commands
Unify existing Cobra help command, and git-help's style.
2017-09-11 09:32:44 -07:00
Shayne Holmes
ee6150f67c Change short help messages to imperative voice
Unify the output of `restic help`.
2017-09-11 09:26:13 -07:00
Alexander Neumann
9fa909ccd6 Update golden files 2017-09-11 17:52:22 +02:00
Alexander Neumann
b1af544b1d Merge pull request #1224 from restic/improve-exclude-caches
Allow multiple exclude-if-present
2017-09-11 17:31:24 +02:00
Dmitriy Morozov
7d5b17ac72 Update man page for snapshots 2017-09-10 15:28:06 -07:00
Dmitriy Morozov
7a221f2473 Run changes through gofmt 2017-09-10 15:09:28 -07:00
Dmitriy Morozov
bdbe956c5c Add --compact option to snapshots
With --compact, snapshots doesn't list directories and puts all tags on a single
line. This way each snapshot takes up exactly one line.
2017-09-10 13:06:43 -07:00
Alexander Neumann
8e5b1e6f2f Add --group-by to manual 2017-09-10 21:00:51 +02:00
Alexander Neumann
257a454515 Add entry to CHANGELOG 2017-09-10 20:55:02 +02:00
Alexander Neumann
b6aeea425b Merge pull request #1196 from mungomat/forget_groupByTagsOnly
forget: group-by-tags-only
2017-09-10 20:52:15 +02:00
Alexander Neumann
c8e05d1f2a Add entry to CHANGELOG 2017-09-10 20:29:08 +02:00
Alexander Neumann
a8aa4eb06c Rename parameter filename -> path 2017-09-10 20:28:21 +02:00
Alexander Neumann
c1a02cc081 Merge pull request #1228 from restic/fix-1204-3
Always use long name for keys
2017-09-10 19:39:23 +02:00
Alexander Neumann
e66adc42da Always use long name for keys
Otherwise the code panics if a file with a short name is tried.
2017-09-10 15:35:10 +02:00
Alexander Neumann
89938bc21c Update manual pages 2017-09-10 15:33:20 +02:00
Alexander Neumann
0b2947dedb Add test for rejectByPattern 2017-09-10 15:31:58 +02:00
Alexander Neumann
47ddd34266 Improve test 2017-09-10 15:20:41 +02:00
Alexander Neumann
2fdca5d310 Improve debug message 2017-09-10 15:16:05 +02:00
Alexander Neumann
e5d4e33509 Improve error message if no targets specified 2017-09-10 15:14:11 +02:00
Alexander Neumann
e117f613af Move device test into new RejectFunc 2017-09-10 15:13:40 +02:00
Alexander Neumann
0dfdf02885 Rework pattern excludes 2017-09-10 14:34:28 +02:00
Alexander Neumann
4a0129fc2b Rename excludeByFile -> rejectIfPresent 2017-09-10 14:25:58 +02:00
Alexander Neumann
a9c705009c Move reject functions to new file 2017-09-10 14:25:25 +02:00
Alexander Neumann
d937ad8cf6 Rename FilenameCheck to RejectFunc
We already have the opposite: pipe.SelectFunc(item string, fi
os.FileInfo) bool, so RejectFunc is a good name.
2017-09-10 14:21:51 +02:00
Alexander Neumann
1a08a8219f Merge pull request #1227 from restic/fix-1204-2
Handle invalid key file
2017-09-10 14:12:51 +02:00
Tobias Klein
9924c311c9 added test cases 2017-09-10 12:23:28 +02:00
Alexander Neumann
e846e14965 Ignore files with invalid name in the repo 2017-09-10 11:00:07 +02:00
Alexander Neumann
36e70228f2 Handle invalid key file 2017-09-10 10:55:01 +02:00
Tobias Klein
a677f1139a removed unnacessary line 2017-09-10 10:41:07 +02:00
Alexander Neumann
6f8eba9c28 Merge pull request #1222 from damiencourousse/manual-typo-fix
manual: typo fix
2017-09-09 21:40:00 +02:00
Alexander Neumann
c22c582546 Allow multiple exclude-if-present 2017-09-09 21:24:29 +02:00
Alexander Neumann
ea75509d6e Print warning for non-existing items 2017-09-09 21:12:41 +02:00
Tobias Klein
ed30bd7b76 gofmt 2017-09-09 18:19:19 +02:00
Damien Couroussé
7090c5ceeb manual: typo fix 2017-09-09 16:58:17 +02:00
Tobias Klein
bee09c1a0f test 2017-09-09 16:33:51 +02:00
Tobias Klein
8f9ef4402b error in case of unknown grouping option 2017-09-09 15:55:37 +02:00
Tobias Klein
f26c0cb70f testcase updated 2017-09-09 15:33:12 +02:00
Tobias Klein
81d7ecba2b manual updated 2017-09-09 13:26:35 +02:00
Tobias Klein
087c3fe1dc tests updated 2017-09-09 13:26:35 +02:00
Tobias Klein
43ff971dfd new sub-option for backup: time
New option to specify the timestamp for a backup
2017-09-09 13:26:35 +02:00
Alexander Neumann
5c75a98053 Merge pull request #1220 from restic/fix-1204
Fix panic when file name is too short
2017-09-09 11:49:23 +02:00
Alexander Neumann
7ce47402fb Merge pull request #1170 from fawick/exclude_caches
Add option to exclude contents of cache directories
2017-09-09 10:56:12 +02:00
Alexander Neumann
1e48141648 Fix panic when file name is too short
Closes #1204
2017-09-09 10:50:32 +02:00
Fabian Wickborn
dbda892542 Add option to exclude directories with a tagfile
The option is named --exclude-if-present and accepts a parameter
filename[:content]. Directories are excluded and their contents is not
backed up if they contain a file with the specified name and,
optionally, that starts with the specified content. The tagfile itself
is never excluded.

There is also a shortcut --exclude-caches that works in the same way as
the likewise-named option of tar(1): Directories are recognized as cache
if they contain a file named "CACHEDIR.TAG.

Closes #317.
2017-09-09 09:57:42 +02:00
Alexander Neumann
b46774be21 Merge pull request #1214 from ricardoseriani/fix-manual.rst-key-remove
Fix manual.rst to use key remove instead of key rm
2017-09-07 21:16:46 +02:00
Tobias Klein
1073bfba37 flexible grouping option for the forget-command 2017-09-06 20:14:18 +02:00
Alexander Neumann
5dfb4d1195 Merge pull request #1209 from restic/handle-colliding-names
Resolve name collisions
2017-09-05 22:33:32 +02:00
Alexander Neumann
0a2219c5f7 Travis: Reduce workload by removing Go tip 2017-09-05 22:08:23 +02:00
Alexander Neumann
ff3149831e Merge pull request #1210 from prattmic/glob_test
filter: document recursive wildcards
2017-09-05 21:50:27 +02:00
Alexander Neumann
c935d0558c Add entry to CHANGELOG 2017-09-05 21:48:13 +02:00
Alexander Neumann
83eb075e3a Resolve name collisions
At the moment when two items to be saved have the same directory name,
restic only saves the first one to the repo. Let's say we have a
structure like this:

    dir1
    └── subdir
        └── file
    dir2
    └── subdir
        └── file

When restic is run on `dir1/subdir` and `dir2/subdir`, it will only save
the first `subdir`:

    $ restic backup dir1/subdir dir2/subdir
    [...]

    $ restic ls -l latest
    drwxr-xr-x  1000   100      0 2017-08-27 20:56:39 /subdir
    -rw-r--r--  1000   100     17 2017-08-27 20:56:39 /subdir/file

That's obviously a bad thing, caused by an early decision to strip the
full path to the files/dirs to save and only leave the last directory.

This commit partly resolves this by handling colliding names and
resolving the conflicts. Restic will now append a counter to the file
(`-123`) until the conflict is resolved. So in the example above, we'll
end up with the following structure:

    $ restic ls -l latest
    drwxr-xr-x  1000   100      0 2017-08-27 20:56:39 /subdir
    -rw-r--r--  1000   100     17 2017-08-27 20:56:39 /subdir/file
    drwxr-xr-x  1000   100      0 2017-08-27 20:56:46 /subdir-1
    -rw-r--r--  1000   100     17 2017-08-27 20:56:46 /subdir-1/file

This partly addresses #549 and closes #1179.

At first I thought that the obvious correction would be to archive the
full path. But it turns out that collisions may still occur: Suppose you
have a file named `foo` in the current directory, and the parent directory
also contains a file `foo`. Archiving these with restic also causes a
collision, since restic strips the `../` from the first file:

    $ restic backup ../foo foo

This also happens with `tar`, which does not handle the collision and
will happily archive two files called `foo`.

So, the best way forward is to handle name collisions and archive the
whole path. The latter will be tackled in a separate PR.
2017-09-05 21:47:02 +02:00
Ricardo Seriani
204c2bf09c Fix manual to use key remove instead of key rm 2017-09-05 16:22:42 -03:00
Alexander Neumann
2444522243 Add test for colliding names 2017-09-05 21:10:02 +02:00
Michael Pratt
92eb1cbffd filter: document recursive wildcards
Match/ChildMatch accept a ** pattern which is not noted in the doc
string, nor do any of the docs or tests specify whether the match is
greedy (i.e., can 'foo/**/bar' match paths with additional intermediate
bar directories?).

Add a note to the doc string and add test cases for greedy matches.
2017-09-04 14:38:48 -07:00
Alexander Neumann
8c40ae5a03 Add entry to CHANGELOG 2017-09-04 21:58:33 +02:00
Alexander Neumann
fa2ee78a5c Merge pull request #1044 from lloeki/982-improve-restore
Improve restore
2017-09-04 21:51:12 +02:00
Tobias Klein
e4a5cdc5bc forget: group-by-tags-only 2017-09-03 17:11:25 +02:00
Tobias Klein
2d73a273af saving a variable 2017-09-03 17:09:55 +02:00
Tobias Klein
761af08889 fix: bucker
bucker "always" does not return a unique id in case of exact same timestamps
2017-09-03 17:09:55 +02:00
Alexander Neumann
0ee1650f82 Merge pull request #1191 from prattmic/profile
debug: properly handle interrupted profiles
2017-09-03 09:49:46 +02:00
Alexander Neumann
0e647417f3 Add entry to CHANGELOG 2017-09-03 09:49:37 +02:00
Alexander Neumann
d1bf5a4882 Merge pull request #1203 from myfreeweb/master
Handle SIGINFO on all supported platforms
2017-09-03 09:44:24 +02:00
Alexander Neumann
b8414b240c Add entry to CHANGELOG 2017-09-03 09:44:15 +02:00
Greg V
3fbdd12b04 Handle SIGINFO on all supported platforms
Not just darwin
2017-09-02 22:06:31 +03:00
Alexander Neumann
a3f6bf3e5a Merge pull request #1202 from restic/fix-manpages
Fix manpages, update Go version
2017-09-02 10:11:43 +02:00
Alexander Neumann
3a5805db50 Update Go versions for CI 2017-09-02 09:29:02 +02:00
Alexander Neumann
de8c64e767 Use deterministic date for man pages 2017-09-02 09:27:11 +02:00
Alexander Neumann
73d6b15095 Merge pull request #1201 from gjmf/patch-1
Fixed word-o. ("Package", not "packet".)
2017-09-01 21:21:18 +02:00
Alexander Neumann
5d396b9302 Merge pull request #1200 from molivier/patch-1
Update manual.rst
2017-09-01 21:21:16 +02:00
Graham Freeman
61d2519111 Fixed word-o. ("Package", not "packet".)
Fixed a word-o. homebrew is a package manager, not a packet manager. :)
2017-08-31 13:34:53 -07:00
Matthieu OLIVIER
e61c94a846 Update manual.rst
`key remove` becomes `key rm`.
2017-08-31 18:26:04 +02:00
Alexander Neumann
7ed0f61f3f Merge pull request #1189 from FiloSottile/patch-2
doc/design: fix keys.data MAC format description
2017-08-29 20:44:31 +02:00
Alexander Neumann
85055d1c68 Merge pull request #1187 from FiloSottile/patch-1
internal/crypto: small simplifications
2017-08-29 20:43:15 +02:00
Michael Pratt
e4c469c149 debug: properly handle interrupted profiles
By default (i.e., without profile.NoShutdownHook), profile.Start listens
for SIGINT and will stop the profile and call os.Exit(0).

restic already listens for SIGINT and runs its own cleanup handlers
before calling os.Exit(0).

As is, these handlers are racing when an interrupt occurs, and in my
experience, restic tends to win the race, resulting in an unusable
profile.

Eliminate the race and properly stop profiles on interrupt by disabling
package profile's signal handler and instead stop the profile in a
restic cleanup handler.
2017-08-28 22:03:26 -07:00
Filippo Valsorda
9940e8d9f1 internal/crypto: small simplifications
* append operates on len, not cap (not a bug since len is set to cap above, but let's avoid the confusion)
* no need to extend ciphertext again to cap after we made it big enough
* make consistent use of ciphertext[:ivSize] vs iv[:]
* make all input problems errors and impossible/catastrophic cases panics
2017-08-29 00:30:06 +02:00
Filippo Valsorda
3dccca1f27 doc/design: fix keys.data MAC format description
"not including the last 32 byte" was wrong, should have been 16 bytes. But the whole description is redundant anyway.
2017-08-29 00:22:11 +02:00
Alexander Neumann
22e96a37f8 Merge pull request #1184 from prattmic/docs
Doc cleanup and mention restore include/exclude
2017-08-28 21:14:48 +02:00
Alexander Neumann
48b1ab5aaf Merge pull request #1182 from restic/fix-1167
local: do not create dirs below data/ for non-existing dir
2017-08-28 21:13:24 +02:00
Alexander Neumann
0230fa188f Add entry to CHANGELOG 2017-08-28 21:13:14 +02:00
Alexander Neumann
4118ce876e Merge pull request #1185 from prattmic/gcs_panic
gs: fix nil dereference
2017-08-28 21:11:30 +02:00
Michael Pratt
9537bc561d gs: fix nil dereference
info can be nil if err != nil, resulting in a nil dereference while
logging:

$ # GCS config
$ ./restic init
debug enabled
panic: runtime error: invalid memory address or nil pointer dereference
[signal SIGSEGV: segmentation violation code=0x1 addr=0x0 pc=0x935947]

goroutine 1 [running]:
github.com/restic/restic/internal/backend/gs.(*Backend).Save(0xc420012690, 0xe84e80, 0xc420010448, 0xb57149, 0x3, 0xc4203fc140, 0x40, 0xe7be40, 0xc4201d8f90, 0xa0, ...)
	src/github.com/restic/restic/internal/backend/gs/gs.go:226 +0x6d7
github.com/restic/restic/internal/repository.AddKey(0xe84e80, 0xc420010448, 0xc4202f0360, 0xc42000a1b0, 0x4, 0x0, 0xa55b60, 0xc4203043e0, 0xa55420)
	src/github.com/restic/restic/internal/repository/key.go:235 +0x4a1
github.com/restic/restic/internal/repository.createMasterKey(0xc4202f0360, 0xc42000a1b0, 0x4, 0xa55420, 0xc420304370, 0x6a6070)
	src/github.com/restic/restic/internal/repository/key.go:62 +0x60
github.com/restic/restic/internal/repository.(*Repository).init(0xc4202f0360, 0xe84e80, 0xc420010448, 0xc42000a1b0, 0x4, 0x1, 0xc42030a440, 0x40, 0x32a4573d3d9eb5, 0x0, ...)
	src/github.com/restic/restic/internal/repository/repository.go:403 +0x5d
github.com/restic/restic/internal/repository.(*Repository).Init(0xc4202f0360, 0xe84e80, 0xc420010448, 0xc42000a1b0, 0x4, 0xe84e40, 0xc42004ad80)
	src/github.com/restic/restic/internal/repository/repository.go:397 +0x12c
main.runInit(0xc420018072, 0x16, 0x0, 0x0, 0x0, 0xe84e40, 0xc42004ad80, 0xc42000a1b0, 0x4, 0xe7dac0, ...)
	src/github.com/restic/restic/cmd/restic/cmd_init.go:47 +0x2a4
main.glob..func9(0xeb5000, 0xedad70, 0x0, 0x0, 0x0, 0x0)
	src/github.com/restic/restic/cmd/restic/cmd_init.go:20 +0x8e
github.com/restic/restic/vendor/github.com/spf13/cobra.(*Command).execute(0xeb5000, 0xedad70, 0x0, 0x0, 0xeb5000, 0xedad70)
	src/github.com/restic/restic/vendor/github.com/spf13/cobra/command.go:649 +0x457
github.com/restic/restic/vendor/github.com/spf13/cobra.(*Command).ExecuteC(0xeb3e00, 0xc420011650, 0xa55b60, 0xc420011660)
	src/github.com/restic/restic/vendor/github.com/spf13/cobra/command.go:728 +0x339
github.com/restic/restic/vendor/github.com/spf13/cobra.(*Command).Execute(0xeb3e00, 0x25, 0xc4201a7eb8)
	src/github.com/restic/restic/vendor/github.com/spf13/cobra/command.go:687 +0x2b
main.main()
	src/github.com/restic/restic/cmd/restic/main.go:72 +0x268

(The error was likely because I had just enabled the GCS API. Subsequent
runs were fine.)
2017-08-27 21:36:04 -07:00
Michael Pratt
ae43c47ca8 doc: add mention of restore --exclude/--include
There is a lot more detail that could be added here, but it is worth
getting things off the ground with at least a mention that it is
possible to restore individual files.

Updates #396
2017-08-27 18:36:00 -07:00
Michael Pratt
2fa4060991 doc: fix inconsistencies
* Replace references to ~/shared/work/web which should be ~/web.

* Replace references to ~/tmp which should be /tmp.

* Restore /home/art to /tmp/restore-art instead of /tmp/restore-work,
  which is clearly a copy/paste from the command above.
2017-08-27 18:35:01 -07:00
Alexander Neumann
f9a934759f sftp: Improve error handling for non-existing dir 2017-08-27 20:53:04 +02:00
Alexander Neumann
3686b1ffe5 local: Create directories below data/ if it exists 2017-08-27 20:52:58 +02:00
Alexander Neumann
ea017a49c3 local: Add test for #1167
It was discovered that restic creates directories when a non-existing
directory is specified as a local repository.
2017-08-27 20:38:46 +02:00
Alexander Neumann
3559f9c776 Merge pull request #1174 from pwaring/patch-1
Update minimum Go version
2017-08-25 21:26:13 +02:00
Paul Waring
637f57ca71 Update minimum Go version
Minimum version is now 1.8 according to build.go (from latest master, cloned a few minutes ago):

```
paul@voga:~/third-dev/restic$ go run build.go 
Go version go1.7.4 detected, restic requires at least Go 1.8
exit status 1
```
2017-08-24 19:51:11 +01:00
Alexander Neumann
4e60156b45 Add entry to CHANGELOG 2017-08-18 19:50:32 +02:00
Alexander Neumann
af9946b098 Merge pull request #1164 from ricardoseriani/fix-key-remove-command
Change key rm command to key remove
2017-08-18 19:49:50 +02:00
Ricardo Seriani
b7d4b0f821 Update man pages 2017-08-17 16:44:28 -03:00
Ricardo Seriani
62ed776a8c Change key rm command to key remove
Change key rm command to key remove, to follow manual and other commands
2017-08-17 11:03:26 -03:00
Loic Nageleisen
f880ff21aa Fixing restore with excluded
An exclude filter is basically a 'wildcard but foo', so even if a
childMayMatch, other children of a dir may not, therefore childMayMatch
does not matter, but we should not go down unless the dir is selected
for restore.
2017-08-16 15:25:02 +02:00
Loic Nageleisen
4a36993c19 Smarter filter when children won't match
This improves restore performance by several orders of magniture by not
going through the whole tree recursively when we can anticipate that no
match will ever occur.
2017-08-16 15:25:02 +02:00
Alexander Neumann
d87b2f189d Merge pull request #1157 from ceh/defer-file-close-after-err-check
internal: check error before deferring file Close()
2017-08-13 19:50:05 +02:00
Alexander Neumann
f9a097a8c0 Merge pull request #1158 from ceh/fix-contributing-typo
Fix contribution typo
2017-08-13 19:49:03 +02:00
Alexander Neumann
d43358b6dd Correct URL to forum 2017-08-13 19:47:54 +02:00
Alexander Neumann
8058f196e1 Merge pull request #1156 from dimejo/patch-1
Readme: Correct link formatting
2017-08-13 19:47:31 +02:00
Emil Hessman
e13e6f34d2 Fix contribution typo 2017-08-13 19:35:53 +02:00
Emil Hessman
c2ff7150aa internal: check error before deferring file Close()
If there is an error, file will be `nil`. We should check the returned error before deferring file `Close()`.
2017-08-13 19:28:13 +02:00
dimejo
a899621930 Readme: Correct link formatting 2017-08-13 15:07:40 +02:00
Alexander Neumann
a0966e1d1d Update README 2017-08-11 21:38:18 +02:00
Alexander Neumann
e2464382ed Update issue template 2017-08-11 21:36:10 +02:00
Alexander Neumann
095bc79dc3 Correct README #2 2017-08-09 21:58:20 +02:00
Alexander Neumann
1fd3c2488e Correct README 2017-08-09 21:57:55 +02:00
Alexander Neumann
2ee8485886 Update README 2017-08-09 21:56:41 +02:00
Alexander Neumann
b67c178672 Merge pull request #1149 from restic/azure-support
Add Azure blob storage as backend
2017-08-09 21:30:35 +02:00
Alexander Neumann
7ac4f0a525 Merge pull request #1134 from restic/gcs-support
Add backend for Google Cloud Storage (GCS)
2017-08-09 21:28:36 +02:00
Alexander Neumann
c4613c51d1 Add note about Go 1.8 2017-08-09 20:33:30 +02:00
Alexander Neumann
77bf17076b Add entry to CHANGELOG 2017-08-09 20:23:29 +02:00
Alexander Neumann
8dd6beba15 gs: Add section to the manual 2017-08-09 20:22:07 +02:00
Alexander Neumann
a345386967 Add a section to the CHANGELOG 2017-08-09 20:15:08 +02:00
Alexander Neumann
bdd43bd430 Add a section to the manual 2017-08-09 20:13:34 +02:00
Alexander Neumann
1716501598 CI: Make sure the GCS backend tests run on Travis 2017-08-06 21:47:56 +02:00
Alexander Neumann
d9a5b9178e gs: Rework path initialization 2017-08-06 21:47:56 +02:00
Alexander Neumann
8ca6a9a240 Vendor dependencies for GCS 2017-08-06 21:47:56 +02:00
Dipta Das
ba75a3884c Add Google Cloud Storage as backend
Environment variables:
GOOGLE_PROJECT_ID=gcp-project-id
GOOGLE_APPLICATION_CREDENTIALS=path-to-json-file

Environment variables for test:
RESTIC_TEST_GS_PROJECT_ID=gcp-project-id
RESTIC_TEST_GS_APPLICATION_CREDENTIALS=path-to-json-file
RESTIC_TEST_GS_REPOSITORY=gs:us-central1/test-bucket

Init repository:
$ restic -r gs🪣/[prefix] init
2017-08-06 21:47:55 +02:00
Alexander Neumann
d91d89eef6 azure: Create container if it does not exist 2017-08-06 21:47:04 +02:00
Alexander Neumann
a726c91116 azure: Rework path initialization 2017-08-06 21:47:04 +02:00
Alexander Neumann
d00fe95f10 Upgrade min Go version to 1.8 2017-08-06 21:47:04 +02:00
Alexander Neumann
072b7a014e azure: User internal errors package 2017-08-06 21:47:04 +02:00
Alexander Neumann
618ce115d7 Azure: Use default HTTP transport 2017-08-06 21:47:04 +02:00
Alexander Neumann
d973aa82fe Vendor dependencies for azure backend 2017-08-06 21:47:04 +02:00
Dipta Das
3a85b6b7c6 Add Azure Blob Storage as backend
Environment variables:
AZURE_ACCOUNT_NAME=storage-account-name
AZURE_ACCOUNT_KEY=storage-account-key

Environment variables for test:
RESTIC_TEST_AZURE_ACCOUNT_NAME=storage-account-name
RESTIC_TEST_AZURE_ACCOUNT_KEY=storage-account-key
RESTIC_TEST_AZURE_REPOSITORY=azure:restic-test-container

Init repository:
$ restic -r azure:container-name:/prefix/dir init
2017-08-06 21:47:04 +02:00
Alexander Neumann
2c22ff175c Merge pull request #1150 from restic/removed-date-from-manpage
Fix manpage generation
2017-08-06 21:46:44 +02:00
Alexander Neumann
6bc43a4198 manpage: Remove auto gen tag from man page 2017-08-06 21:31:01 +02:00
Alexander Neumann
e348b3deeb manpage: Do not panic when no command is given 2017-08-06 21:01:49 +02:00
Alexander Neumann
6724b9a583 Merge pull request #1148 from restic/update-simple-scrypt
Lock simple-scrypt library to master branch
2017-08-05 20:04:33 +02:00
Alexander Neumann
41c35b2218 Lock simple-scrypt library to master branch
The master branch includes a fix for i386, otherwise the calibration
panics. See https://github.com/restic/restic/issues/676 for details.
2017-08-05 19:24:56 +02:00
Alexander Neumann
4477d76f03 Merge pull request #1147 from restic/add-manpage
Add new command 'manpage'
2017-08-05 12:39:42 +02:00
Alexander Neumann
14f5f6235a Add entry to CHANGELOG 2017-08-05 12:05:53 +02:00
Alexander Neumann
739350fd8e backup: Do not print hostname in help text
This is necessary so that the manpage generation is deterministic and we
can test if the man pages are up to date when the CI tests run.
2017-08-05 12:05:53 +02:00
Alexander Neumann
14ed97102b Add instructions for developers 2017-08-05 12:05:53 +02:00
Alexander Neumann
db389058fa Add generated manual pages 2017-08-05 12:05:53 +02:00
Alexander Neumann
b557d04007 CI: Fix test for forbidden packages 2017-08-05 12:05:53 +02:00
Alexander Neumann
52c5da997b Add CI test for manpages 2017-08-05 12:05:53 +02:00
Alexander Neumann
57d198f99a Vendor dependencies for manpage generation 2017-08-05 11:08:49 +02:00
Alexander Neumann
a3ab17b470 Add 'manpage' command to generate manual pages 2017-08-05 10:57:01 +02:00
Alexander Neumann
9bf3141893 Add entry to CHANGELOG 2017-08-01 22:02:08 +02:00
Alexander Neumann
d35eb6a0c3 Merge pull request #1144 from wjkohnen/close-exclude-files
Close exclude files and check errors
2017-08-01 22:01:43 +02:00
Johannes Kohnen
37aad2e3aa Close exclude files and check errors 2017-08-01 17:34:27 +00:00
Alexander Neumann
efc5d0699a Merge pull request #1139 from donat-b/pwfile-doesnt-exist
Error message in case PasswordFile is missing
2017-07-27 15:41:50 +02:00
donat
893bc9f777 Error message in case PasswordFile is missing 2017-07-27 14:23:08 +03:00
Alexander Neumann
61b8729ef9 Merge pull request #1138 from stapelberg/patch-1
fix typo: explicitely → explicitly
2017-07-27 09:01:47 +02:00
Michael Stapelberg
b89d3cc4d0 fix typo: explicitely → explicitly 2017-07-27 08:24:53 +02:00
Alexander Neumann
e8cc11ea34 Add entry to CHANGELOG 2017-07-26 22:29:39 +02:00
Alexander Neumann
2e804511ca Merge pull request #1133 from middelink/fix-1132
Force restic to ask the password when adding a key.
2017-07-26 22:11:37 +02:00
Alexander Neumann
b6790c491b Merge pull request #1131 from restic/test-cleanups
Rework withTestEnvironment
2017-07-26 22:06:06 +02:00
Pauline Middelink
c95e2b009e Simpify cmd_backup and cmd_init now we have the password in gopts. 2017-07-24 23:32:55 +02:00
Pauline Middelink
d5615a67c8 Refactor password resolving.
Instead of determining the password lazily during ReadPassword(), do so now in
cobra.PersistentPreRunE() so we can store the result in the globalOptions and
reuse/override when applicable without having to worry about the environment
or flag options interfering.
2017-07-24 23:05:37 +02:00
Pauline Middelink
d9b9bbd4a8 Force restic to ask the password when adding a key.
As `restic key add` uses the same `ReadPasswordTwice()` as the
rest of restic, it is sensitive to the environment variable
RESTIC_PASSWORD or --password-file= override.

When asking for the new key, temporary remove these 2 overrides, forcing
the password to be asked.
2017-07-24 22:00:44 +02:00
Alexander Neumann
d780b1eede Rework withTestEnvironment
Switch from a function passed as a parameter to a cleanup function,
which is also executed when the test function panics, so no temporary
directories are left behind.
2017-07-24 21:32:34 +02:00
Alexander Neumann
608adf15a3 Merge pull request #1130 from middelink/fix-fuse-test
Fuse testing leaves test mountpoint around.
2017-07-24 21:26:05 +02:00
Pauline Middelink
1717391f6c Fuse testing leaves test mountpoint around. Move it under the testing tree which is removed after each test. 2017-07-24 20:33:39 +02:00
Alexander Neumann
2e6e9ff6f8 Merge pull request #1129 from restic/move-restic-pkg
Move restic package to internal/restic
2017-07-24 19:19:28 +02:00
Alexander Neumann
23c903074c Move restic package to internal/restic 2017-07-24 17:43:32 +02:00
Alexander Neumann
94030a12cf Add entry to CHANGELOG 2017-07-23 16:54:56 +02:00
Alexander Neumann
f63d7de9da Merge pull request #1126 from restic/switch-to-default-go-git-layout
Use idiomatic default Go git repo layout
2017-07-23 16:54:53 +02:00
Alexander Neumann
13ee6792df Add remark about GOPATH on forked repos 2017-07-23 16:36:13 +02:00
Alexander Neumann
6302444f34 Remove linebreak from Errorf() format string 2017-07-23 15:51:44 +02:00
Alexander Neumann
61c5e4b54a Fix glyphcheck 2017-07-23 15:51:39 +02:00
Alexander Neumann
d6118871be Update other files 2017-07-23 14:40:05 +02:00
Alexander Neumann
94b27e8933 Fix paths for tests 2017-07-23 14:39:57 +02:00
Alexander Neumann
05500dc5f8 Update documentation 2017-07-23 14:32:31 +02:00
Alexander Neumann
c5a72971fe Remove Vagrantfile 2017-07-23 14:30:33 +02:00
Alexander Neumann
5bc6486e3b Update docs 2017-07-23 14:25:39 +02:00
Alexander Neumann
59e18bce0a Fix build.go 2017-07-23 14:25:39 +02:00
Alexander Neumann
898c5b6df5 Fix integration tests 2017-07-23 14:25:39 +02:00
Alexander Neumann
9cd422791a Update build.go 2017-07-23 14:25:38 +02:00
Alexander Neumann
91edebf1fe Vendor dependencies with dep 2017-07-23 14:25:38 +02:00
Alexander Neumann
df8a5792f1 Remove Dockerfile 2017-07-23 14:25:38 +02:00
Alexander Neumann
cda7b417cd Remove envrc 2017-07-23 14:25:38 +02:00
Alexander Neumann
d2ac35af26 Remove vendor 2017-07-23 14:25:37 +02:00
Alexander Neumann
6caeff2408 Run goimports 2017-07-23 14:21:03 +02:00
Alexander Neumann
83d1a46526 Moves files 2017-07-23 14:19:13 +02:00
Alexander Neumann
d1bd160b0a Merge pull request #1061 from bclermont/docker-image
add docker image
2017-07-22 11:52:22 +02:00
Alexander Neumann
bc88a8bb03 Add entry to CHANGELOG 2017-07-22 11:52:16 +02:00
Alexander Neumann
04cfb984ae Add VERSION file for 0.7.1 2017-07-22 11:04:32 +02:00
Alexander Neumann
02a245941a Adapt CHANGELOG for 0.7.1 2017-07-22 11:03:44 +02:00
Alexander Neumann
7fb1352aa1 Merge pull request #1124 from restic/use-minio-300
Set minio-go to v3.0.0
2017-07-22 11:01:57 +02:00
Alexander Neumann
4c555bad2e Set minio-go to v3.0.0 2017-07-22 10:19:52 +02:00
Alexander Neumann
75c789bab4 Merge pull request #1122 from restic/swift-remove-range-test
swift: Remove check for byte range
2017-07-21 23:05:05 +02:00
Alexander Neumann
626d020e62 swift: Remove check for byte range
Closes #1084
Closes #1094
2017-07-21 20:45:25 +02:00
Alexander Neumann
3830117735 Merge pull request #1121 from restic/fix-swift-test
swift: Increase backend test delay for removed file
2017-07-21 20:37:10 +02:00
Alexander Neumann
042cee8e36 Merge pull request #1117 from donat-b/password-file-env
Set default value for password-file flag from env
2017-07-21 19:47:49 +02:00
Alexander Neumann
03cc5b47e9 appveyor: Update Go version to 1.8.3 2017-07-21 19:42:34 +02:00
Alexander Neumann
46fa45942e swift: Increase backend test delay for removed file 2017-07-21 19:42:34 +02:00
Alexander Neumann
0cb4104aa7 Fix Go report card URLs (thanks @tyll) 2017-07-20 22:31:41 +02:00
donat
f2bbc5fbc4 Set default value for password-file flag from env
Allows defining password file path as RESTIC_PASSWORD_FILE=/foo
2017-07-20 10:47:02 +03:00
Alexander Neumann
16340ce811 Merge pull request #1090 from middelink/fix-1081
Update HasTags() and HasPaths() to follow #1081 feature request
2017-07-19 17:11:18 +02:00
Alexander Neumann
2cf8153f4a Add entry to CHANGELOG 2017-07-19 17:09:02 +02:00
Alexander Neumann
2f00287e45 Merge pull request #1112 from restic/fix-chmod-not-supported
Ignore error for Chmod() on FS that don't support it
2017-07-19 17:05:18 +02:00
Alexander Neumann
0de17f64e9 Add entry to CHANGELOG 2017-07-19 17:02:53 +02:00
Alexander Neumann
c30838878f Merge pull request #1115 from restic/fix-prune-index
prune: Fix newly created index
2017-07-19 17:01:11 +02:00
Alexander Neumann
bd31281f1e prune: Fix newly created index
This fixes a bug introduced in c79fb6fcdd
where the index file after a prune contains pack files that do not exist
any more. Restic will detect this during backup and abort with an error
message until the user runs `prune` or `rebuild-index` again.
2017-07-18 23:10:30 +02:00
Alexander Neumann
7fc54ed98e Improve test case for prune 2017-07-18 23:07:29 +02:00
Alexander Neumann
0abdcedcab Ignore error for Chmod() on FS that don't support it
See #1079
2017-07-18 21:47:30 +02:00
Alexander Neumann
6c05353086 Add entry to CHANGELOG 2017-07-17 22:02:06 +02:00
Alexander Neumann
e7575bf380 Merge pull request #1108 from restic/update-deps
Update vendored deps
2017-07-17 22:01:52 +02:00
Alexander Neumann
89ace85903 s3: Use streaming API and remove workarounds 2017-07-17 20:43:53 +02:00
Alexander Neumann
68a91d66b7 s3: Use new API for CopyObject 2017-07-17 20:43:45 +02:00
Alexander Neumann
724b5bf4fe Update minio-go 2017-07-17 20:19:04 +02:00
Alexander Neumann
d6da9211bc Update vendored deps (except minio-go) 2017-07-17 20:00:44 +02:00
Alexander Neumann
f45abac27f Merge pull request #1107 from bclermont/fix-s3-panic
Fix S3 panic on Invalid configuration
2017-07-17 18:50:46 +02:00
Bruno Clermont
00b9a1d87d evaluate open error 2017-07-17 11:33:19 +03:00
Alexander Neumann
20b835b5a4 Improve help text 2017-07-16 21:41:13 +02:00
Alexander Neumann
7bb1a474df Add entry to CHANGELOG 2017-07-16 21:41:13 +02:00
Alexander Neumann
750ee35dbf Add more examples to the manual 2017-07-16 21:40:53 +02:00
Alexander Neumann
fda5e1f543 Adress code review comments 2017-07-16 21:40:53 +02:00
Alexander Neumann
78d090aea5 Implement TagList and TagLists as pflag.Value 2017-07-16 21:40:53 +02:00
Alexander Neumann
7362569cf5 Use TagLists for all commands 2017-07-16 21:40:53 +02:00
Alexander Neumann
f5b1c7e5f1 Add TagList 2017-07-16 21:40:53 +02:00
Pauline Middelink
c554cdac4c Update HasTags() and HasPaths() to follow #1081 idea
Replace all but 3 occurences of StringSliceVar to StringArrayVar. This will
prevent the flag parser to interpretate the given values as CSV string.

Both --tag, --keep-tag and --path can be given multiple times, the command will
match snapshots matching ANY of the tags/paths. Only when a value is given which
contains a comma separated list of tags/paths, ALL elements need to match.
2017-07-16 21:40:53 +02:00
Alexander Neumann
41b624ea1b Merge pull request #1105 from restic/improve-sftp-open
sftp: Improve check for data subdirs
2017-07-16 15:41:13 +02:00
Alexander Neumann
7cdcaadcf5 Add entry to CHANGELOG 2017-07-16 15:11:26 +02:00
Alexander Neumann
4ad33d3c3b sftp: Improve check for data subdirs 2017-07-16 15:10:06 +02:00
Alexander Neumann
2778ac21de Merge pull request #1103 from tobya/docsupdate
Update links to design.md to design.rst
2017-07-16 10:39:53 +02:00
Toby Allen
cb3cd57926 Update links to design.md to design.rst 2017-07-15 19:35:45 +01:00
Alexander Neumann
ba6815d413 Merge pull request #1100 from fawick/master
Allow absolute target path in build.go
2017-07-15 10:11:45 +02:00
Fabian Wickborn
52004cdde8 Allow absolute target path in build.go
Fixes #1099.
2017-07-14 11:54:46 +02:00
Alexander Neumann
1d2045cb61 Test error for os.PathError
See https://github.com/restic/restic/issues/1079#issuecomment-315177469
for details.
2017-07-13 21:29:29 +02:00
Alexander Neumann
357e2e404a Merge pull request #1080 from restic/fix-1079
local: Ignore ENOTSUP error for chmod
2017-07-13 20:12:47 +02:00
Alexander Neumann
38e5640cda Add CHANGELOG entry 2017-07-09 21:43:05 +02:00
Alexander Neumann
c4c731bd9a Merge pull request #1082 from Habbie/siginfo
support SIGINFO on Darwin
2017-07-09 21:41:29 +02:00
Alexander Neumann
04d27acd60 Add entry to CHANGELOG 2017-07-05 20:54:37 +02:00
Alexander Neumann
80f0303b21 Merge pull request #1086 from kamsz/iam
Add support for IAM instance profile
2017-07-05 20:53:31 +02:00
Kamil Szczygieł
d651d9b427 more verbose debug 2017-07-05 19:21:57 +02:00
Kamil Szczygieł
3b2648bd5e iam instance profile 2017-07-05 16:19:25 +02:00
Peter van Dijk
73cc11f000 support SIGINFO on Darwin 2017-07-03 20:39:42 +02:00
Alexander Neumann
637de0149c Add entry to CHANGELOG 2017-07-03 19:49:18 +02:00
Alexander Neumann
855575e5a7 Merge pull request #1077 from restic/create-subdirs
local/sftp: Auto-create subdirs of data/ on init/open
2017-07-03 19:47:58 +02:00
Alexander Neumann
ed2999a163 Merge pull request #1075 from restic/migrate-s3-continue
s3: Improve migration to new layout
2017-07-03 19:47:22 +02:00
Alexander Neumann
a18c16e19e local: Ignore ENOTSUP error for chmod
Closes: #1079
2017-07-03 19:45:56 +02:00
Alexander Neumann
9032ab2eec local/sftp: Create dirs on open() 2017-07-02 19:35:45 +02:00
Alexander Neumann
03f66b8d74 Create subdirs below data/ 2017-07-02 19:35:45 +02:00
Alexander Neumann
8c30ae7c65 Add entry to CHANGELOG 2017-07-02 11:21:05 +02:00
Alexander Neumann
453c9c9199 s3 migrate layout: Retry on errors 2017-07-02 11:15:20 +02:00
Alexander Neumann
993e370f92 s3 migrate layout: Ignore already renamed files 2017-07-02 10:47:50 +02:00
Alexander Neumann
2bcd3a3acc s3 migrate layout: Rename key files last 2017-07-02 10:47:20 +02:00
Alexander Neumann
c54c632ca1 s3 migrate layout: Force old layout for rename 2017-07-02 10:47:03 +02:00
Alexander Neumann
28a4a35625 Allow migrate to run althoug check failed 2017-07-02 10:29:41 +02:00
Alexander Neumann
e7577d7bb4 Add stub to CHANGELOG 2017-07-01 15:11:36 +02:00
Bruno Clermont
d702227af0 install fuse and ca-certificates 2017-06-23 10:38:19 +02:00
Bruno Clermont
b7251dbea5 add docker image 2017-06-23 10:28:18 +02:00
4948 changed files with 2738604 additions and 99571 deletions

1
.envrc
View File

@@ -1 +0,0 @@
GOPATH=$PWD:$PWD/vendor

View File

@@ -1,25 +1,55 @@
<!--
NOTE: Not filling out the issue template needs a good reason, otherwise the
issue may be closed instantly! Please take the time to help us debugging the
problem by collecting information, even if it seems irrelevant to you. Thanks!
NOTE: Not filling out the issue template needs a good reason, otherwise it may
take a lot longer to find the problem! Please take the time to help us
debugging the problem by collecting information, even if it seems irrelevant to
you. Thanks!
If you have a question, the forum at https://discourse.restic.net is a better place.
Please do not create issues for usage or documentation questions! We're using
the GitHub issue tracker mainly for tracking bugs and feature requests.
-->
## Output of `restic version`
## How did you start restic exactly? (Include the complete command line)
## How did you run restic exactly?
<!--
This section should include at least:
* The complete command line and any environment variables you used to
configure restic's backend access. Make sure to replace sensitive values!
* The output of the commands, what restic prints gives may give us much
information to diagnose the problem!
-->
## What backend/server/service did you use?
## What backend/server/service did you use to store the repository?
## Expected behavior
<!--
Describe what you'd like restic to do differently.
-->
## Actual behavior
<!--
In this section, please try to concentrate on observations, so only describe
what you observed directly.
-->
## Steps to reproduce the behavior
<!--
The more time you spend describing an easy way to reproduce the behavior (if
this is possible), the easier it is for the project developers to fix it!
-->
## Do you have any idea what may have caused this?
## Do you have an idea how to solve the issue?

3
.gitignore vendored
View File

@@ -1,6 +1,3 @@
/pkg
/bin
/restic
/.vagrant
/vendor/pkg
/doc/_build

View File

@@ -2,9 +2,8 @@ language: go
sudo: false
go:
- 1.7.6
- 1.8.3
- tip
- 1.8.x
- 1.9.x
os:
- linux
@@ -17,19 +16,15 @@ env:
matrix:
exclude:
- os: osx
go: 1.7.6
- os: osx
go: tip
go: 1.8.x
- os: linux
go: 1.8.3
go: 1.9.x
include:
- os: linux
go: 1.8.3
go: 1.9.x
sudo: true
env:
RESTIC_TEST_FUSE=1
allow_failures:
- go: tip
branches:
only:

View File

@@ -1,7 +1,297 @@
This file describes changes relevant to all users that are made in each
released version of restic from the perspective of the user.
Important Changes in 0.X.Y
Important Changes in 0.8.0
==========================
* A vulnerability was found in the restic restorer, which allowed attackers in
special circumstances to restore files to a location outside of the target
directory. Due to the circumstances we estimate this to be a low-risk
vulnerability, but urge all users to upgrade to the latest version of restic.
Exploiting the vulnerability requires a Linux/Unix system which saves
backups via restic and a Windows systems which restores files from the repo.
In addition, the attackers need to be able to create create files with
arbitrary names which are then saved to the restic repo. For example, by
creating a file named "..\test.txt" (which is a perfectly legal filename on
Linux) and restoring a snapshot containing this file on Windows, it would be
written to the parent of the target directory.
We'd like to thank Tyler Spivey for reporting this responsibly!
https://github.com/restic/restic/pull/1445
* The s3 backend used the subdir `restic` within a bucket if no explicit path
after the bucket name was specified. Since this version, restic does not use
this default path any more. If you created a repo on s3 in a bucket without
specifying a path within the bucket, you need to add `/restic` at the end of
the repository specification to access your repo: `s3:s3.amazonaws.com/bucket/restic`
https://github.com/restic/restic/issues/1292
https://github.com/restic/restic/pull/1437
* We've added a local cache for metadata so that restic doesn't need to load
all metadata (snapshots, indexes, ...) from the repo each time it starts. By
default the cache is active, but there's a new global option `--no-cache`
that can be used to disable the cache. By deafult, the cache a standard
cache folder for the OS, which can be overridden with `--cache-dir`. The
cache will automatically populate, indexes and snapshots are saved as they
are loaded.
https://github.com/restic/restic/pull/1040
https://github.com/restic/restic/issues/29
https://github.com/restic/restic/issues/738
https://github.com/restic/restic/issues/282
https://github.com/restic/restic/pull/1287
* A related change was to by default create pack files in the repo that
contain either data or metadata, not both mixed together. This allows easy
caching of only the metadata files. The next run of `restic prune` will
untangle mixed files automatically.
https://github.com/restic/restic/pull/1265
* The Google Cloud Storage backend no longer requires the service account to
have the `storage.buckets.get` permission ("Storage Admin" role) in `restic
init` if the bucket already exists.
https://github.com/restic/restic/pull/1281
* Added support for rate limiting through `--limit-upload` and
`--limit-download` flags.
https://github.com/restic/restic/issues/1216
https://github.com/restic/restic/pull/1336
https://github.com/restic/restic/pull/1358
* Failed backend requests are now automatically retried.
https://github.com/restic/restic/pull/1353
* We've added the `dump` command which prints a file from a snapshot to
stdout. This can e.g. be used to restore files read with `backup --stdin`.
https://github.com/restic/restic/issues/510
https://github.com/restic/restic/pull/1346
Small changes
-------------
* The directory structure in the fuse mount now exposes a symlink `latest`
which points to the latest snapshot in that particular directory.
https://github.com/restic/restic/pull/1249
* The option `--compact` was added to the `forget` command to provide the same
compact view as the `snapshots` command.
https://github.com/restic/restic/pull/1269
* We've re-enabled a workaround for `minio-go` (the library we're using to
access s3 backends), this reduces memory usage.
https://github.com/restic/restic/issues/1256
https://github.com/restic/restic/pull/1267
* The sftp backend now prompts for the password if a password is necessary for
login.
https://github.com/restic/restic/issues/448
https://github.com/restic/restic/pull/1270
* The `generate` command has been added, which replaces the now removed
commands `manpage` and `autocomplete`. This release of restic contains the
most recent manpages in `doc/man` and the auto-completion files for bash and
zsh in `doc/bash-completion.sh` and `doc/zsh-completion.zsh`
https://github.com/restic/restic/issues/1274
https://github.com/restic/restic/pull/1282
* A bug was discovered in the library we're using to access Backblaze, it now
reuses already established TCP connections which should be a lot faster and
not cause network failures any more.
https://github.com/restic/restic/issues/1291
https://github.com/restic/restic/pull/1301
* Another bug in the `forget` command caused `prune` not to be run when
`--prune` was specified without a policy, e.g. when only snapshot IDs that
should be forgotten are listed manually. This is corrected now.
https://github.com/restic/restic/pull/1317
* The `check` command now explicetly prints `No errors were found` when no
errors could be found.
https://github.com/restic/restic/pull/1319
https://github.com/restic/restic/issues/1303
* The fuse mount now has an `ids` subdirectory which contains the snapshots
below their (short) IDs.
https://github.com/restic/restic/issues/1102
https://github.com/restic/restic/pull/1299
https://github.com/restic/restic/pull/1320
* The `backup` command was improved, it now caches the result of excludes for
a directory.
https://github.com/restic/restic/issues/1271
https://github.com/restic/restic/pull/1326
* We've added the `--cacert` option which can be used to pass one (or more) CA
certificates to restic. These are used in addition to the system CA
certificates to verify HTTPS certificates (e.g. for the REST backend).
https://github.com/restic/restic/issues/1114
https://github.com/restic/restic/pull/1276
* When the list of files/dirs to be saved is read from a file with
`--files-from`, comment lines (starting with `#`) are now ignored.
https://github.com/restic/restic/issues/1367
https://github.com/restic/restic/pull/1368
Important Changes in 0.7.3
==========================
* For large backups stored in Google Cloud Storage, the `prune` command fails
because listing only returns the first 1000 files. This has been corrected,
no data is lost in the process. In addition, a plausibility check was added
to `prune`.
https://github.com/restic/restic/issues/1246
https://github.com/restic/restic/pull/1247
Important Changes in 0.7.2
==========================
* We've added an official docker image and a Dockerfile to build this image in
`docker/`.
https://github.com/restic/restic/pull/1061
* The git repository layout was changed to resemble the layout typically used
in Go projects, we're not using `gb` for building restic any more and
vendoring the dependencies is now taken care of by `dep`.
https://github.com/restic/restic/pull/1126
* We now support saving backups on Google Cloud Storage.
https://github.com/restic/restic/pull/1134
https://github.com/restic/restic/pull/1052
https://github.com/restic/restic/issues/211
* We've added support for Microsoft Azure Blob Storage as a restic backend.
https://github.com/restic/restic/pull/1149
https://github.com/restic/restic/pull/1059
https://github.com/restic/restic/issues/609
* In the course of supporting Microsoft Azure Blobe Storage Go 1.8 is now a
requirement to build restic.
* The `restore` command has been improved: When dirs are excluded (or not
included) in a restore, they are not loaded from the repo any more.
https://github.com/restic/restic/pull/1044
* Name collisions are now resolved by appending a counter.
https://github.com/restic/restic/issues/1179
https://github.com/restic/restic/pull/1209
Small changes
-------------
* The `key` command now prompts for a password even if the original password
to access a repo has been specified via the `RESTIC_PASSWORD` environment
variable or a password file.
https://github.com/restic/restic/issues/1132
https://github.com/restic/restic/pull/1133
* Properly report errors when reading files with exclude patterns.
https://github.com/restic/restic/pull/1144
* We now automatically generate man pages for all restic commands, see the
subdir `doc/man`.
https://github.com/restic/restic/issues/697
https://github.com/restic/restic/pull/1147
* The `key remove` command was corrected and now works as documented.
https://github.com/restic/restic/pull/1164
* When a restic command other than `init` is used with a local repository and
the repository directory does not exist, restic creates the directory
structure. That's an error, only the `init` command should create the dir.
https://github.com/restic/restic/issues/1167
https://github.com/restic/restic/pull/1182
* Restic now prints stats on all BSD systems (not only on darwin) when SIGINFO
is received (usually when ctrl+t is pressed).
https://github.com/restic/restic/pull/1203
https://github.com/restic/restic/pull/1082#issuecomment-326279920
* Since a few releases restic had the ability to write profiling files for
memory and CPU usage when `debug` is enabled. It was discovered that when
restic is interrupted (ctrl+c is pressed), the proper shutdown hook is not
run. This is now corrected.
https://github.com/restic/restic/pull/1191
* A new option `--exclude-caches` was added that allows excluding cache
directories (that are tagged as such). This is a special case of a more
generic option `--exclude-if-present` which excludes a directory if a file
with a specific name (and contents) is present.
https://github.com/restic/restic/issues/317
https://github.com/restic/restic/pull/1170
https://github.com/restic/restic/pull/1224
* The `forget` command now has an option `--group-by` that allows flexible
grouping policies.
https://github.com/restic/restic/pull/1196
* The date and time restic records for a new backup can now be specified
externally by passing `--time` to the `backup` command.
https://github.com/restic/restic/pull/1205
* The option `--compact` was added to the `snapshots` command to get a better
overview of the snapshots in a repo. It limits each snapshot to a single
line.
https://github.com/restic/restic/issues/1218
https://github.com/restic/restic/pull/1223
Important Changes in 0.7.1
==========================
* The `migrate` command for chaning the `s3legacy` layout to the `default`
layout for s3 backends has been improved: It can now be restarted with
`restic migrate --force s3_layout` and automatically retries operations on
error.
https://github.com/restic/restic/issues/1073
https://github.com/restic/restic/pull/1075
Small changes
-------------
* The local and sftp backends now create the subdirs below `data/` on
open/init. This way, restic makes sure that they always exist. This is
connected to an issue for the sftp server:
https://github.com/restic/rest-server/pull/11#issuecomment-309879710
https://github.com/restic/restic/issues/1055
https://github.com/restic/restic/pull/1077
https://github.com/restic/restic/pull/1105
* When no S3 credentials are specified in the environment variables, restic
now tries to load credentials from an IAM instance profile when the s3
backend is used.
https://github.com/restic/restic/issues/1067
https://github.com/restic/restic/pull/1086
* On Darwin and FreeBSD, restic now prints stats when SIGINFO is received
(usually when ctrl+t is pressed).
https://github.com/restic/restic/pull/1082
* The dependencies have been updated.
https://github.com/restic/restic/pull/1108
https://github.com/restic/restic/pull/1124
* A bug was found (and corrected) in the index rebuilding after prune, which
led to indexes which include blobs that were not present in the repo any
more. There were already checks in place which detected this situation and
aborted with an error message. A new run of either `prune` or
`rebuild-index` corrected the index files. This is now fixed and a test has
been added to detect this.
https://github.com/restic/restic/pull/1115
* Errors for chmod() on Unix for filesystems which do not support it (e.g. smb
mounted via gvfs) are now ignored.
https://github.com/restic/restic/pull/1080
https://github.com/restic/restic/pull/1112
* The semantic for the `--tags` option to `forget` and `snapshots` was
clarified:
https://github.com/restic/restic/issues/1081
https://github.com/restic/restic/pull/1090
Important Changes in 0.7.0
==========================
* New "swift" backend: A new backend for the OpenStack Swift cloud storage

View File

@@ -60,50 +60,35 @@ uploading it somewhere or post only the parts that are really relevant.
Development Environment
=======================
For development you need the build tool [`gb`](https://getgb.io), it can be
installed by running the following command:
In order to compile restic with the `go` tool directly, it needs to be checked
out at the right path within a `GOPATH`. The concept of a `GOPATH` is explained
in ["How to write Go code"](https://golang.org/doc/code.html).
$ go get github.com/constabulary/gb/...
The repository contains two directories with code: `src/` contains the code
written for restic, whereas `vendor/` contains copies of libraries restic
depends on. The libraries are managed with the `gb vendor` command.
Just clone the repository, `cd` to it and run `gb build` to build the binary:
If you do not have a directory with Go code yet, executing the following
instructions in your shell will create one for you and check out the restic
repo:
$ export GOPATH="$HOME/go"
$ mkdir -p "$GOPATH/src/github.com/restic"
$ cd "$GOPATH/src/github.com/restic"
$ git clone https://github.com/restic/restic
$ cd restic
$ gb build
[...]
$ bin/restic version
You can then build restic as follows:
$ go build ./cmd/restic
$ ./restic version
restic compiled manually
compiled at unknown time with go1.7
compiled with go1.8.3 on linux/amd64
The following commands can be used to run all the tests:
$ gb test
ok github.com/restic/restic 8.174s
[...]
$ go test ./cmd/... ./internal/...
If you want to run your tests on Linux, OpenBSD or FreeBSD, you can use
[vagrant](https://www.vagrantup.com/) with the provided `Vagrantfile` to
quickly set up VMs and run the tests, e.g.:
$ vagrant up freebsd
[...]
$ vagrant ssh freebsd -c 'cd restic/restic; go test -v ./...'
[...]
The default `go` tool can also be used by setting the environment variable
`GOPATH` to the following value while being in the top level directory in the
git repository:
$ export GOPATH=$PWD:$PWD/vendor
The file `.envrc` allows automatic `GOPATH` configuration with
[direnv](https://direnv.net/), inspect the file and then allow automatic
configuration by running `direnv allow`.
The repository contains two sets of directories with code: `cmd/` and
`internal/` contain the code written for restic, whereas `vendor/` contains
copies of libraries restic depends on. The libraries are managed with the
[`dep`](https://github.com/golang/dep) tool.
Providing Patches
=================
@@ -122,7 +107,8 @@ down to the following steps:
2. Clone the repository locally and create a new branch. If you are working on
the code itself, please set up the development environment as described in
the previous section.
the previous section. Especially take care to place your forked repository
at the correct path (`src/github.com/restic/restic`) within your `GOPATH`.
3. Then commit your changes as fine grained as possible, as smaller patches,
that handle one and only one issue are easier to discuss and merge.
@@ -144,7 +130,7 @@ down to the following steps:
would I need to be aware of with this change.
8. Once your code looks good and passes all the tests, we'll merge it. Thanks
a low for your contribution!
a lot for your contribution!
Please provide the patches for each bug or feature in a separate branch and
open up a pull request for each.
@@ -170,7 +156,7 @@ what the tests are there for.
Git Commits
-----------
I would be good if you could follow the same general style regarding Git
It would be good if you could follow the same general style regarding Git
commits as the rest of the project, this makes reviewing code, browsing the
history and triaging bugs much easier.

View File

@@ -1,57 +0,0 @@
# This Dockerfiles configures a container that is similar to the Travis CI
# environment and can be used to run tests locally.
#
# build the image:
# docker build -t restic/test .
#
# run all tests and cross-compile restic:
# docker run --rm -v $PWD:/home/travis/restic restic/test go run run_integration_tests.go -minio minio
#
# run interactively:
# docker run --interactive --tty --rm -v $PWD:/home/travis/restic restic/test /bin/bash
#
# run a subset of tests:
# docker run --rm -v $PWD:/home/travis/restic restic/test gb test -v ./backend
#
# build the image for an older version of Go:
# docker build --build-arg GOVERSION=1.6.4 -t restic/test:go1.6.4 .
FROM ubuntu:14.04
ARG GOVERSION=1.8.3
ARG GOARCH=amd64
# install dependencies
RUN apt-get update
RUN apt-get install -y --no-install-recommends ca-certificates wget git build-essential openssh-server
# add and configure user
ENV HOME /home/travis
RUN useradd -m -d $HOME -s /bin/bash travis
# run everything below as user travis
USER travis
WORKDIR $HOME
# download and install Go
RUN wget -q -O /tmp/go.tar.gz https://storage.googleapis.com/golang/go${GOVERSION}.linux-${GOARCH}.tar.gz
RUN tar xf /tmp/go.tar.gz && rm -f /tmp/go.tar.gz
ENV GOROOT $HOME/go
ENV GOPATH $HOME/gopath
ENV PATH $PATH:$GOROOT/bin:$GOPATH/bin:$HOME/bin
RUN mkdir -p $HOME/restic
# pre-install tools, this speeds up running the tests itself
RUN go get github.com/constabulary/gb/...
RUN go get golang.org/x/tools/cmd/cover
RUN go get github.com/mitchellh/gox
RUN go get github.com/pierrre/gotestcover
RUN mkdir $HOME/bin \
&& wget -q -O $HOME/bin/minio https://dl.minio.io/server/minio/release/linux-${GOARCH}/minio \
&& chmod +x $HOME/bin/minio
# set TRAVIS_BUILD_DIR for integration script
ENV TRAVIS_BUILD_DIR $HOME/restic
WORKDIR $HOME/restic

219
Gopkg.lock generated Normal file
View File

@@ -0,0 +1,219 @@
# This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'.
[[projects]]
branch = "master"
name = "bazil.org/fuse"
packages = [".","fs","fuseutil"]
revision = "371fbbdaa8987b715bdd21d6adc4c9b20155f748"
[[projects]]
name = "cloud.google.com/go"
packages = ["compute/metadata"]
revision = "eaddaf6dd7ee35fd3c2420c8d27478db176b0485"
version = "v0.15.0"
[[projects]]
name = "github.com/Azure/azure-sdk-for-go"
packages = ["storage"]
revision = "509eea43b93cec2f3f17acbe2578ef58703923f8"
version = "v11.1.1-beta"
[[projects]]
name = "github.com/Azure/go-autorest"
packages = ["autorest","autorest/adal","autorest/azure","autorest/date"]
revision = "7aa5b8a6f18b5c15910c767ab005fc4585221177"
version = "v9.1.1"
[[projects]]
name = "github.com/cenkalti/backoff"
packages = ["."]
revision = "61153c768f31ee5f130071d08fc82b85208528de"
version = "v1.1.0"
[[projects]]
name = "github.com/cpuguy83/go-md2man"
packages = ["md2man"]
revision = "1d903dcb749992f3741d744c0f8376b4bd7eb3e1"
version = "v1.0.7"
[[projects]]
name = "github.com/dgrijalva/jwt-go"
packages = ["."]
revision = "dbeaa9332f19a944acb5736b4456cfcc02140e29"
version = "v3.1.0"
[[projects]]
branch = "master"
name = "github.com/dustin/go-humanize"
packages = ["."]
revision = "77ed807830b4df581417e7f89eb81d4872832b72"
[[projects]]
name = "github.com/elithrar/simple-scrypt"
packages = ["."]
revision = "2325946f714c95de4a6088202c402fbdfa64163b"
version = "v1.2.0"
[[projects]]
name = "github.com/go-ini/ini"
packages = ["."]
revision = "5b3e00af70a9484542169a976dcab8d03e601a17"
version = "v1.30.0"
[[projects]]
branch = "master"
name = "github.com/golang/protobuf"
packages = ["proto"]
revision = "1643683e1b54a9e88ad26d98f81400c8c9d9f4f9"
[[projects]]
name = "github.com/inconshreveable/mousetrap"
packages = ["."]
revision = "76626ae9c91c4f2a10f34cad8ce83ea42c93bb75"
version = "v1.0"
[[projects]]
branch = "master"
name = "github.com/juju/ratelimit"
packages = ["."]
revision = "5b9ff866471762aa2ab2dced63c9fb6f53921342"
[[projects]]
branch = "master"
name = "github.com/kr/fs"
packages = ["."]
revision = "2788f0dbd16903de03cb8186e5c7d97b69ad387b"
[[projects]]
name = "github.com/kurin/blazer"
packages = ["b2","base","internal/b2types","internal/blog"]
revision = "e269a1a17bb6aec278c06a57cb7e8f8d0d333e04"
version = "v0.2.1"
[[projects]]
branch = "master"
name = "github.com/minio/go-homedir"
packages = ["."]
revision = "21304a94172ae3a09dee2cd86a12fb6f842138c7"
[[projects]]
name = "github.com/minio/minio-go"
packages = [".","pkg/credentials","pkg/encrypt","pkg/policy","pkg/s3signer","pkg/s3utils","pkg/set"]
revision = "4e0f567303d4cc90ceb055a451959fb9fc391fb9"
version = "3.0.3"
[[projects]]
branch = "master"
name = "github.com/ncw/swift"
packages = ["."]
revision = "c95c6e5c2d1a3d37fc44c8c6dc9e231c7500667d"
[[projects]]
name = "github.com/pkg/errors"
packages = ["."]
revision = "645ef00459ed84a119197bfb8d8205042c6df63d"
version = "v0.8.0"
[[projects]]
name = "github.com/pkg/profile"
packages = ["."]
revision = "5b67d428864e92711fcbd2f8629456121a56d91f"
version = "v1.2.1"
[[projects]]
name = "github.com/pkg/sftp"
packages = ["."]
revision = "98203f5a8333288eb3163b7c667d4260fe1333e9"
version = "1.0.0"
[[projects]]
name = "github.com/pkg/xattr"
packages = ["."]
revision = "23c75e3f6c1d8b13b3dd905b011a7f38a06044b7"
version = "v0.2.1"
[[projects]]
name = "github.com/restic/chunker"
packages = ["."]
revision = "db83917be3b88cc307464b7d8a221c173e34a0db"
version = "v0.2.0"
[[projects]]
name = "github.com/russross/blackfriday"
packages = ["."]
revision = "4048872b16cc0fc2c5fd9eacf0ed2c2fedaa0c8c"
version = "v1.5"
[[projects]]
name = "github.com/satori/uuid"
packages = ["."]
revision = "879c5887cd475cd7864858769793b2ceb0d44feb"
version = "v1.1.0"
[[projects]]
name = "github.com/sirupsen/logrus"
packages = ["."]
revision = "f006c2ac4710855cf0f916dd6b77acf6b048dc6e"
version = "v1.0.3"
[[projects]]
branch = "master"
name = "github.com/spf13/cobra"
packages = [".","doc"]
revision = "7b2c5ac9fc04fc5efafb60700713d4fa609b777b"
[[projects]]
name = "github.com/spf13/pflag"
packages = ["."]
revision = "e57e3eeb33f795204c1ca35f56c44f83227c6e66"
version = "v1.0.0"
[[projects]]
branch = "master"
name = "golang.org/x/crypto"
packages = ["curve25519","ed25519","ed25519/internal/edwards25519","pbkdf2","poly1305","scrypt","ssh","ssh/terminal"]
revision = "edd5e9b0879d13ee6970a50153d85b8fec9f7686"
[[projects]]
branch = "master"
name = "golang.org/x/net"
packages = ["context","context/ctxhttp"]
revision = "cd69bc3fc700721b709c3a59e16e24c67b58f6ff"
[[projects]]
branch = "master"
name = "golang.org/x/oauth2"
packages = [".","google","internal","jws","jwt"]
revision = "bb50c06baba3d0c76f9d125c0719093e315b5b44"
[[projects]]
branch = "master"
name = "golang.org/x/sys"
packages = ["unix","windows"]
revision = "8dbc5d05d6edcc104950cc299a1ce6641235bc86"
[[projects]]
branch = "master"
name = "google.golang.org/api"
packages = ["gensupport","googleapi","googleapi/internal/uritemplates","storage/v1"]
revision = "7afc123cf726cd2f253faa3e144d2ab65477b18f"
[[projects]]
name = "google.golang.org/appengine"
packages = [".","internal","internal/app_identity","internal/base","internal/datastore","internal/log","internal/modules","internal/remote_api","internal/urlfetch","urlfetch"]
revision = "150dc57a1b433e64154302bdc40b6bb8aefa313a"
version = "v1.0.0"
[[projects]]
branch = "v2"
name = "gopkg.in/yaml.v2"
packages = ["."]
revision = "eb3733d160e74a9c7e442f435eb3bea458e1d19f"
[solve-meta]
analyzer-name = "dep"
analyzer-version = 1
inputs-digest = "f0a207197cb502238ac87ca8e07b2640c02ec380a50b036e09ef87e40e31ca2d"
solver-name = "gps-cdcl"
solver-version = 1

21
Gopkg.toml Normal file
View File

@@ -0,0 +1,21 @@
# Gopkg.toml example
#
# Refer to https://github.com/golang/dep/blob/master/docs/Gopkg.toml.md
# for detailed Gopkg.toml documentation.
#
# required = ["github.com/user/thing/cmd/thing"]
# ignored = ["github.com/user/project/pkgX", "bitbucket.org/user/project/pkgA/pkgY"]
#
# [[constraint]]
# name = "github.com/user/project"
# version = "1.0.0"
#
# [[constraint]]
# name = "github.com/user/project2"
# branch = "dev"
# source = "github.com/myfork/project2"
#
# [[override]]
# name = "github.com/x/y"
# version = "2.4.0"

18
LICENSE
View File

@@ -1,19 +1,21 @@
BSD 2-Clause License
Copyright (c) 2014, Alexander Neumann <alexander@bumpern.de>
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
* Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
* Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR

View File

@@ -6,7 +6,8 @@ restic:
go run build.go
clean:
rm -rf restic
rm -f restic
test:
go test ./...
go test ./cmd/... ./internal/...

View File

@@ -1,4 +1,4 @@
|Documentation| |Build Status| |Build status| |Report Card| |Say Thanks|
|Documentation| |Build Status| |Build status| |Report Card| |Say Thanks| |TestCoverage|
Introduction
------------
@@ -7,11 +7,13 @@ restic is a backup program that is fast, efficient and secure.
For detailed usage and installation instructions check out the `documentation <https://restic.readthedocs.io/en/latest>`__.
You can ask questions in our `Discourse forum <https://forum.restic.net>`__.
Quick start
-----------
Once you've `installed
<https://restic.readthedocs.io/en/latest/installation.html>`__ restic, start
<https://restic.readthedocs.io/en/latest/020_installation.html>`__ restic, start
off with creating a repository for your backups:
.. code-block:: console
@@ -35,7 +37,26 @@ and add some data:
duration: 0:29, 54.47MiB/s
snapshot 40dc1520 saved
For more options check out the `manual guide <https://restic.readthedocs.io/en/latest/manual.html>`__.
Next you can either use ``restic restore`` to restore files or use ``restic
mount`` to mount the repository via fuse and browse the files from previous
snapshots.
For more options check out the `online documentation <https://restic.readthedocs.io/en/latest/>`__.
Backends
--------
Saving a backup on the same machine is nice but not a real backup strategy.
Therefore, restic supports the following backends for storing backups natively:
- `Local directory <https://restic.readthedocs.io/en/latest/030_preparing_a_new_repo.html#local>`__
- `sftp server (via SSH) <https://restic.readthedocs.io/en/latest/030_preparing_a_new_repo.html#sftp>`__
- `HTTP REST server <https://restic.readthedocs.io/en/latest/030_preparing_a_new_repo.html#rest-server>`__ (`protocol <doc/rest_backend.rst>`__ `rest-server <https://github.com/restic/rest-server>`__)
- `AWS S3 <https://restic.readthedocs.io/en/latest/030_preparing_a_new_repo.html#amazon-s3>`__ (either from Amazon or using the `Minio <https://minio.io>`__ server)
- `OpenStack Swift <https://restic.readthedocs.io/en/latest/030_preparing_a_new_repo.html#openstack-swift>`__
- `BackBlaze B2 <https://restic.readthedocs.io/en/latest/030_preparing_a_new_repo.html#backblaze-b2>`__
- `Microsoft Azure Blob Storage <https://restic.readthedocs.io/en/latest/030_preparing_a_new_repo.html#microsoft-azure-blob-storage>`__
- `Google Cloud Storage <https://restic.readthedocs.io/en/latest/030_preparing_a_new_repo.html#google-cloud-storage>`__
Design Principles
-----------------
@@ -86,7 +107,7 @@ the `development blog <https://restic.github.io/blog/>`__.
License
-------
Restic is licensed under "BSD 2-Clause License". You can find the
Restic is licensed under `BSD 2-Clause License <https://opensource.org/licenses/BSD-2-Clause>`__. You can find the
complete text in ``LICENSE``.
.. |Documentation| image:: https://readthedocs.org/projects/restic/badge/?version=latest
@@ -95,7 +116,9 @@ complete text in ``LICENSE``.
:target: https://travis-ci.org/restic/restic
.. |Build status| image:: https://ci.appveyor.com/api/projects/status/nuy4lfbgfbytw92q/branch/master?svg=true
:target: https://ci.appveyor.com/project/fd0/restic/branch/master
.. |Report Card| image:: http://goreportcard.com/badge/github.com/restic/restic
:target: http://goreportcard.com/report/github.com/restic/restic
.. |Report Card| image:: https://goreportcard.com/badge/github.com/restic/restic
:target: https://goreportcard.com/report/github.com/restic/restic
.. |Say Thanks| image:: https://img.shields.io/badge/Say%20Thanks-!-1EAEDB.svg
:target: https://saythanks.io/to/restic
.. |TestCoverage| image:: https://codecov.io/gh/restic/restic/branch/master/graph/badge.svg
:target: https://codecov.io/gh/restic/restic

View File

@@ -1 +1 @@
0.7.0
0.8.0

124
Vagrantfile vendored
View File

@@ -1,124 +0,0 @@
# -*- mode: ruby -*-
# vi: set ft=ruby :
GO_VERSION = '1.7'
def packages_freebsd
return <<-EOF
pkg install -y git
pkg install -y curl
echo 'fuse_load="YES"' >> /boot/loader.conf
echo 'vfs.usermount=1' >> /etc/sysctl.conf
kldload fuse
sysctl vfs.usermount=1
pw groupmod operator -M vagrant
EOF
end
def packages_openbsd
return <<-EOF
. ~/.profile
pkg_add git curl bash gtar--
ln -sf /usr/local/bin/gtar /usr/local/bin/tar
EOF
end
def packages_linux
return <<-EOF
apt-get update
apt-get install -y git curl
EOF
end
def packages_darwin
return <<-EOF
ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)"
brew cask install osxfuse
EOF
end
def install_gimme
return <<-EOF
rm -rf /opt/gimme
mkdir -p /opt/gimme || true
git clone https://github.com/meatballhat/gimme /opt/gimme
perl -p -i -e 's,/bin/bash,/usr/bin/env bash,' /opt/gimme/gimme
ln -sf /opt/gimme/gimme /usr/bin/gimme
EOF
end
def prepare_user(boxname)
return <<-EOF
mkdir -p ~/go/src
export PATH=/usr/local/bin:$PATH
gimme #{GO_VERSION} >> ~/.profile
echo export 'GOPATH=/vagrant/go' >> ~/.profile
echo export 'PATH=$GOPATH/bin:/usr/local/bin:$PATH' >> ~/.profile
. ~/.profile
go get golang.org/x/tools/cmd/cover
go get github.com/constabulary/gb/...
echo
echo "Run:"
echo " vagrant rsync #{boxname}"
echo " vagrant ssh #{boxname} -c 'cd /vagrant; gb build && gb test'"
EOF
end
def fix_perms
return <<-EOF
chown -R vagrant /vagrant
EOF
end
# All Vagrant configuration is done below. The "2" in Vagrant.configure
# configures the configuration version (we support older styles for
# backwards compatibility). Please don't change it unless you know what
# you're doing.
Vagrant.configure(2) do |config|
# use rsync to copy content to the folder
config.vm.synced_folder ".", "/vagrant", :type => "rsync"
# fix permissions on synced folder
config.vm.provision "fix perms", :type => :shell, :inline => fix_perms
config.vm.define "linux" do |b|
b.vm.box = "ubuntu/trusty64"
b.vm.provision "packages linux", :type => :shell, :inline => packages_linux
b.vm.provision "install gimme", :type => :shell, :inline => install_gimme
b.vm.provision "prepare user", :type => :shell, :privileged => false, :inline => prepare_user("linux")
# fix network card
config.vm.provider "virtualbox" do |v|
v.customize ["modifyvm", :id, "--nictype1", "virtio"]
end
end
config.vm.define "freebsd" do |b|
b.vm.box = "geoffgarside/freebsd-10.1"
b.vm.provision "packages freebsd", :type => :shell, :inline => packages_freebsd
b.vm.provision "install gimme", :type => :shell, :inline => install_gimme
b.vm.provision "prepare user", :type => :shell, :privileged => false, :inline => prepare_user("freebsd")
end
config.vm.define "openbsd" do |b|
b.vm.box = "tmatilai/openbsd-5.6"
b.vm.provision "packages openbsd", :type => :shell, :inline => packages_openbsd
b.vm.provision "install gimme", :type => :shell, :inline => install_gimme
b.vm.provision "prepare user", :type => :shell, :privileged => false, :inline => prepare_user("openbsd")
end
config.vm.define "darwin" do |b|
#b.vm.box = "jhcook/osx-yosemite-10.10"
b.vm.box = "jhcook/yosemite-clitools"
b.vm.provision "packages darwin", :type => :shell, :privileged => false, :inline => packages_darwin
b.vm.provision "install gimme", :type => :shell, :inline => install_gimme
b.vm.provision "prepare user", :type => :shell, :privileged => false, :inline => prepare_user("darwin")
end
end

View File

@@ -17,8 +17,8 @@ init:
install:
- rmdir c:\go /s /q
- appveyor DownloadFile https://storage.googleapis.com/golang/go1.8.1.windows-amd64.msi
- msiexec /i go1.8.1.windows-amd64.msi /q
- appveyor DownloadFile https://storage.googleapis.com/golang/go1.9.windows-amd64.msi
- msiexec /i go1.9.windows-amd64.msi /q
- go version
- go env
- appveyor DownloadFile http://sourceforge.netcologne.de/project/gnuwin32/tar/1.13-1/tar-1.13-1-bin.zip -FileName tar.zip

View File

@@ -27,10 +27,12 @@ var config = struct {
Main string
Tests []string
}{
Name: "restic", // name of the program executable and directory
Namespace: "", // subdir of GOPATH, e.g. "github.com/foo/bar"
Main: "cmds/restic", // package name for the main package
Tests: []string{"restic/...", "cmds/..."}, // tests to run
Name: "restic", // name of the program executable and directory
Namespace: "github.com/restic/restic", // subdir of GOPATH, e.g. "github.com/foo/bar"
Main: "github.com/restic/restic/cmd/restic", // package name for the main package
Tests: []string{ // tests to run
"github.com/restic/restic/internal/...",
"github.com/restic/restic/cmd/..."},
}
// specialDir returns true if the file begins with a special character ('.' or '_').
@@ -77,7 +79,12 @@ func excludePath(name string) bool {
// └── restic
// └── foo.go
func updateGopath(dst, src, prefix string) error {
verbosePrintf("copy contents of %v to %v\n", src, filepath.Join(dst, prefix))
return filepath.Walk(src, func(name string, fi os.FileInfo, err error) error {
if name == src {
return err
}
if specialDir(name) {
if fi.IsDir() {
return filepath.SkipDir
@@ -86,6 +93,10 @@ func updateGopath(dst, src, prefix string) error {
return nil
}
if err != nil {
return err
}
if fi.IsDir() {
return nil
}
@@ -291,8 +302,8 @@ func (cs Constants) LDFlags() string {
func main() {
ver := runtime.Version()
if strings.HasPrefix(ver, "go1") && ver < "go1.7" {
fmt.Fprintf(os.Stderr, "Go version %s detected, restic requires at least Go 1.7\n", ver)
if strings.HasPrefix(ver, "go1") && ver < "go1.8" {
fmt.Fprintf(os.Stderr, "Go version %s detected, restic requires at least Go 1.8\n", ver)
os.Exit(1)
}
@@ -368,13 +379,13 @@ func main() {
}
verbosePrintf("create GOPATH at %v\n", gopath)
if err = updateGopath(gopath, filepath.Join(root, "src"), config.Namespace); err != nil {
if err = updateGopath(gopath, root, config.Namespace); err != nil {
die("copying files from %v/src to %v/src failed: %v\n", root, gopath, err)
}
vendor := filepath.Join(root, "vendor", "src")
vendor := filepath.Join(root, "vendor")
if directoryExists(vendor) {
if err = updateGopath(gopath, vendor, ""); err != nil {
if err = updateGopath(gopath, vendor, filepath.Join(config.Namespace, "vendor")); err != nil {
die("copying files from %v to %v failed: %v\n", root, gopath, err)
}
}
@@ -401,7 +412,10 @@ func main() {
if err != nil {
die("Getwd() returned %v\n", err)
}
output := filepath.Join(cwd, outputFilename)
output := outputFilename
if !filepath.IsAbs(output) {
output = filepath.Join(cwd, output)
}
version := getVersion()
constants := Constants{}

View File

@@ -4,7 +4,7 @@ import (
"syscall"
"unsafe"
"restic/debug"
"github.com/restic/restic/internal/debug"
)
// IsProcessBackground returns true if it is running in the background or false if not

92
cmd/restic/cleanup.go Normal file
View File

@@ -0,0 +1,92 @@
package main
import (
"fmt"
"os"
"os/signal"
"sync"
"syscall"
"github.com/restic/restic/internal/debug"
)
var cleanupHandlers struct {
sync.Mutex
list []func() error
done bool
ch chan os.Signal
}
var stderr = os.Stderr
func init() {
cleanupHandlers.ch = make(chan os.Signal)
go CleanupHandler(cleanupHandlers.ch)
InstallSignalHandler()
}
// InstallSignalHandler listens for SIGINT and SIGPIPE, and triggers the cleanup handlers.
func InstallSignalHandler() {
signal.Notify(cleanupHandlers.ch, syscall.SIGINT)
signal.Notify(cleanupHandlers.ch, syscall.SIGPIPE)
}
// SuspendSignalHandler removes the signal handler for SIGINT and SIGPIPE.
func SuspendSignalHandler() {
signal.Reset(syscall.SIGINT)
signal.Reset(syscall.SIGPIPE)
}
// AddCleanupHandler adds the function f to the list of cleanup handlers so
// that it is executed when all the cleanup handlers are run, e.g. when SIGINT
// is received.
func AddCleanupHandler(f func() error) {
cleanupHandlers.Lock()
defer cleanupHandlers.Unlock()
// reset the done flag for integration tests
cleanupHandlers.done = false
cleanupHandlers.list = append(cleanupHandlers.list, f)
}
// RunCleanupHandlers runs all registered cleanup handlers
func RunCleanupHandlers() {
cleanupHandlers.Lock()
defer cleanupHandlers.Unlock()
if cleanupHandlers.done {
return
}
cleanupHandlers.done = true
for _, f := range cleanupHandlers.list {
err := f()
if err != nil {
fmt.Fprintf(stderr, "error in cleanup handler: %v\n", err)
}
}
cleanupHandlers.list = nil
}
// CleanupHandler handles the SIGINT and SIGPIPE signals.
func CleanupHandler(c <-chan os.Signal) {
for s := range c {
debug.Log("signal %v received, cleaning up", s)
fmt.Fprintf(stderr, "%ssignal %v received, cleaning up\n", ClearLine(), s)
code := 0
if s != syscall.SIGINT {
code = 1
}
Exit(code)
}
}
// Exit runs the cleanup handlers and then terminates the process with the
// given exit code.
func Exit(code int) {
RunCleanupHandlers()
os.Exit(code)
}

520
cmd/restic/cmd_backup.go Normal file
View File

@@ -0,0 +1,520 @@
package main
import (
"bufio"
"context"
"fmt"
"io"
"os"
"path/filepath"
"strings"
"time"
"github.com/spf13/cobra"
"github.com/restic/restic/internal/archiver"
"github.com/restic/restic/internal/debug"
"github.com/restic/restic/internal/errors"
"github.com/restic/restic/internal/fs"
"github.com/restic/restic/internal/restic"
)
var cmdBackup = &cobra.Command{
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
given as the arguments.
`,
PreRun: func(cmd *cobra.Command, args []string) {
if backupOptions.Hostname == "" {
hostname, err := os.Hostname()
if err != nil {
debug.Log("os.Hostname() returned err: %v", err)
return
}
backupOptions.Hostname = hostname
}
},
DisableAutoGenTag: true,
RunE: func(cmd *cobra.Command, args []string) error {
if backupOptions.Stdin && backupOptions.FilesFrom == "-" {
return errors.Fatal("cannot use both `--stdin` and `--files-from -`")
}
if backupOptions.Stdin {
return readBackupFromStdin(backupOptions, globalOptions, args)
}
return runBackup(backupOptions, globalOptions, args)
},
}
// BackupOptions bundles all options for the backup command.
type BackupOptions struct {
Parent string
Force bool
Excludes []string
ExcludeFiles []string
ExcludeOtherFS bool
ExcludeIfPresent []string
ExcludeCaches bool
Stdin bool
StdinFilename string
Tags []string
Hostname string
FilesFrom string
TimeStamp string
}
var backupOptions BackupOptions
func init() {
cmdRoot.AddCommand(cmdBackup)
f := cmdBackup.Flags()
f.StringVar(&backupOptions.Parent, "parent", "", "use this parent snapshot (default: last snapshot in the repo that has the same target files/directories)")
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.ExcludeFiles, "exclude-file", nil, "read exclude patterns from a `file` (can be specified multiple times)")
f.BoolVarP(&backupOptions.ExcludeOtherFS, "one-file-system", "x", false, "exclude other file systems")
f.StringArrayVar(&backupOptions.ExcludeIfPresent, "exclude-if-present", nil, "takes filename[:header], exclude contents of directories containing filename (except filename itself) if header of that file is as provided (can be specified multiple times)")
f.BoolVar(&backupOptions.ExcludeCaches, "exclude-caches", false, `excludes cache directories that are marked with a CACHEDIR.TAG file`)
f.BoolVar(&backupOptions.Stdin, "stdin", false, "read backup from stdin")
f.StringVar(&backupOptions.StdinFilename, "stdin-filename", "stdin", "file name to use when reading from stdin")
f.StringArrayVar(&backupOptions.Tags, "tag", nil, "add a `tag` for the new snapshot (can be specified multiple times)")
f.StringVar(&backupOptions.Hostname, "hostname", "", "set the `hostname` for the snapshot manually. To prevent an expensive rescan use the \"parent\" flag")
f.StringVar(&backupOptions.FilesFrom, "files-from", "", "read the files to backup from file (can be combined with file args)")
f.StringVar(&backupOptions.TimeStamp, "time", "", "time of the backup (ex. '2012-11-01 22:08:41') (default: now)")
}
func newScanProgress(gopts GlobalOptions) *restic.Progress {
if gopts.Quiet {
return nil
}
p := restic.NewProgress()
p.OnUpdate = func(s restic.Stat, d time.Duration, ticker bool) {
if IsProcessBackground() {
return
}
PrintProgress("[%s] %d directories, %d files, %s", formatDuration(d), s.Dirs, s.Files, formatBytes(s.Bytes))
}
p.OnDone = func(s restic.Stat, d time.Duration, ticker bool) {
PrintProgress("scanned %d directories, %d files in %s\n", s.Dirs, s.Files, formatDuration(d))
}
return p
}
func newArchiveProgress(gopts GlobalOptions, todo restic.Stat) *restic.Progress {
if gopts.Quiet {
return nil
}
archiveProgress := restic.NewProgress()
var bps, eta uint64
itemsTodo := todo.Files + todo.Dirs
archiveProgress.OnUpdate = func(s restic.Stat, d time.Duration, ticker bool) {
if IsProcessBackground() {
return
}
sec := uint64(d / time.Second)
if todo.Bytes > 0 && sec > 0 && ticker {
bps = s.Bytes / sec
if s.Bytes >= todo.Bytes {
eta = 0
} else if bps > 0 {
eta = (todo.Bytes - s.Bytes) / bps
}
}
itemsDone := s.Files + s.Dirs
status1 := fmt.Sprintf("[%s] %s %s/s %s / %s %d / %d items %d errors ",
formatDuration(d),
formatPercent(s.Bytes, todo.Bytes),
formatBytes(bps),
formatBytes(s.Bytes), formatBytes(todo.Bytes),
itemsDone, itemsTodo,
s.Errors)
status2 := fmt.Sprintf("ETA %s ", formatSeconds(eta))
if w := stdoutTerminalWidth(); w > 0 {
maxlen := w - len(status2) - 1
if maxlen < 4 {
status1 = ""
} else if len(status1) > maxlen {
status1 = status1[:maxlen-4]
status1 += "... "
}
}
PrintProgress("%s%s", status1, status2)
}
archiveProgress.OnDone = func(s restic.Stat, d time.Duration, ticker bool) {
fmt.Printf("\nduration: %s, %s\n", formatDuration(d), formatRate(todo.Bytes, d))
}
return archiveProgress
}
func newArchiveStdinProgress(gopts GlobalOptions) *restic.Progress {
if gopts.Quiet {
return nil
}
archiveProgress := restic.NewProgress()
var bps uint64
archiveProgress.OnUpdate = func(s restic.Stat, d time.Duration, ticker bool) {
if IsProcessBackground() {
return
}
sec := uint64(d / time.Second)
if s.Bytes > 0 && sec > 0 && ticker {
bps = s.Bytes / sec
}
status1 := fmt.Sprintf("[%s] %s %s/s", formatDuration(d),
formatBytes(s.Bytes),
formatBytes(bps))
if w := stdoutTerminalWidth(); w > 0 {
maxlen := w - len(status1)
if maxlen < 4 {
status1 = ""
} else if len(status1) > maxlen {
status1 = status1[:maxlen-4]
status1 += "... "
}
}
PrintProgress("%s", status1)
}
archiveProgress.OnDone = func(s restic.Stat, d time.Duration, ticker bool) {
fmt.Printf("\nduration: %s, %s\n", formatDuration(d), formatRate(s.Bytes, d))
}
return archiveProgress
}
// filterExisting returns a slice of all existing items, or an error if no
// items exist at all.
func filterExisting(items []string) (result []string, err error) {
for _, item := range items {
_, err := fs.Lstat(item)
if err != nil && os.IsNotExist(errors.Cause(err)) {
Warnf("%v does not exist, skipping\n", item)
continue
}
result = append(result, item)
}
if len(result) == 0 {
return nil, errors.Fatal("all target directories/files do not exist")
}
return
}
func readBackupFromStdin(opts BackupOptions, gopts GlobalOptions, args []string) error {
if len(args) != 0 {
return errors.Fatal("when reading from stdin, no additional files can be specified")
}
if opts.StdinFilename == "" {
return errors.Fatal("filename for backup from stdin must not be empty")
}
if gopts.password == "" {
return errors.Fatal("unable to read password from stdin when data is to be read from stdin, use --password-file or $RESTIC_PASSWORD")
}
repo, err := OpenRepository(gopts)
if err != nil {
return err
}
lock, err := lockRepo(repo)
defer unlockRepo(lock)
if err != nil {
return err
}
err = repo.LoadIndex(context.TODO())
if err != nil {
return err
}
r := &archiver.Reader{
Repository: repo,
Tags: opts.Tags,
Hostname: opts.Hostname,
}
_, id, err := r.Archive(context.TODO(), opts.StdinFilename, os.Stdin, newArchiveStdinProgress(gopts))
if err != nil {
return err
}
Verbosef("archived as %v\n", id.Str())
return nil
}
// readFromFile will read all lines from the given filename and write them to a
// string array, if filename is empty readFromFile returns and empty string
// array. If filename is a dash (-), readFromFile will read the lines from
// the standard input.
func readLinesFromFile(filename string) ([]string, error) {
if filename == "" {
return nil, nil
}
var r io.Reader = os.Stdin
if filename != "-" {
f, err := os.Open(filename)
if err != nil {
return nil, err
}
defer f.Close()
r = f
}
var lines []string
scanner := bufio.NewScanner(r)
for scanner.Scan() {
line := scanner.Text()
// ignore empty lines
if line == "" {
continue
}
// strip comments
if strings.HasPrefix(line, "#") {
continue
}
lines = append(lines, line)
}
if err := scanner.Err(); err != nil {
return nil, err
}
return lines, nil
}
func runBackup(opts BackupOptions, gopts GlobalOptions, args []string) error {
if opts.FilesFrom == "-" && gopts.password == "" {
return errors.Fatal("unable to read password from stdin when data is to be read from stdin, use --password-file or $RESTIC_PASSWORD")
}
fromfile, err := readLinesFromFile(opts.FilesFrom)
if err != nil {
return err
}
// merge files from files-from into normal args so we can reuse the normal
// args checks and have the ability to use both files-from and args at the
// same time
args = append(args, fromfile...)
if len(args) == 0 {
return errors.Fatal("nothing to backup, please specify target files/dirs")
}
target := make([]string, 0, len(args))
for _, d := range args {
if a, err := filepath.Abs(d); err == nil {
d = a
}
target = append(target, d)
}
target, err = filterExisting(target)
if err != nil {
return err
}
// rejectFuncs collect functions that can reject items from the backup
var rejectFuncs []RejectFunc
// allowed devices
if opts.ExcludeOtherFS {
f, err := rejectByDevice(target)
if err != nil {
return err
}
rejectFuncs = append(rejectFuncs, f)
}
// add patterns from file
if len(opts.ExcludeFiles) > 0 {
opts.Excludes = append(opts.Excludes, readExcludePatternsFromFiles(opts.ExcludeFiles)...)
}
if len(opts.Excludes) > 0 {
rejectFuncs = append(rejectFuncs, rejectByPattern(opts.Excludes))
}
if opts.ExcludeCaches {
opts.ExcludeIfPresent = append(opts.ExcludeIfPresent, "CACHEDIR.TAG:Signature: 8a477f597d28d172789f06886806bc55")
}
rc := &rejectionCache{}
for _, spec := range opts.ExcludeIfPresent {
f, err := rejectIfPresent(spec, rc)
if err != nil {
return err
}
rejectFuncs = append(rejectFuncs, f)
}
repo, err := OpenRepository(gopts)
if err != nil {
return err
}
lock, err := lockRepo(repo)
defer unlockRepo(lock)
if err != nil {
return err
}
// exclude restic cache
if repo.Cache != nil {
f, err := rejectResticCache(repo)
if err != nil {
return err
}
rejectFuncs = append(rejectFuncs, f)
}
err = repo.LoadIndex(context.TODO())
if err != nil {
return err
}
var parentSnapshotID *restic.ID
// Force using a parent
if !opts.Force && opts.Parent != "" {
id, err := restic.FindSnapshot(repo, opts.Parent)
if err != nil {
return errors.Fatalf("invalid id %q: %v", opts.Parent, err)
}
parentSnapshotID = &id
}
// Find last snapshot to set it as parent, if not already set
if !opts.Force && parentSnapshotID == nil {
id, err := restic.FindLatestSnapshot(context.TODO(), repo, target, []restic.TagList{}, opts.Hostname)
if err == nil {
parentSnapshotID = &id
} else if err != restic.ErrNoSnapshotFound {
return err
}
}
if parentSnapshotID != nil {
Verbosef("using parent snapshot %v\n", parentSnapshotID.Str())
}
Verbosef("scan %v\n", target)
selectFilter := func(item string, fi os.FileInfo) bool {
for _, reject := range rejectFuncs {
if reject(item, fi) {
return false
}
}
return true
}
stat, err := archiver.Scan(target, selectFilter, newScanProgress(gopts))
if err != nil {
return err
}
arch := archiver.New(repo)
arch.Excludes = opts.Excludes
arch.SelectFilter = selectFilter
arch.Warn = func(dir string, fi os.FileInfo, err error) {
// TODO: make ignoring errors configurable
Warnf("%s\rwarning for %s: %v\n", ClearLine(), dir, err)
}
timeStamp := time.Now()
if opts.TimeStamp != "" {
timeStamp, err = time.Parse(TimeFormat, opts.TimeStamp)
if err != nil {
return errors.Fatalf("error in time option: %v\n", err)
}
}
_, id, err := arch.Snapshot(context.TODO(), newArchiveProgress(gopts, stat), target, opts.Tags, opts.Hostname, parentSnapshotID, timeStamp)
if err != nil {
return err
}
Verbosef("snapshot %s saved\n", id.Str())
return nil
}
func readExcludePatternsFromFiles(excludeFiles []string) []string {
var excludes []string
for _, filename := range excludeFiles {
err := func() (err error) {
file, err := fs.Open(filename)
if err != nil {
return err
}
defer func() {
// return pre-close error if there was one
if errClose := file.Close(); err == nil {
err = errClose
}
}()
scanner := bufio.NewScanner(file)
for scanner.Scan() {
line := strings.TrimSpace(scanner.Text())
// ignore empty lines
if line == "" {
continue
}
// strip comments
if strings.HasPrefix(line, "#") {
continue
}
line = os.ExpandEnv(line)
excludes = append(excludes, line)
}
return scanner.Err()
}()
if err != nil {
Warnf("error reading exclude patterns: %v:", err)
return nil
}
}
return excludes
}

View File

@@ -8,18 +8,19 @@ import (
"github.com/spf13/cobra"
"restic"
"restic/backend"
"restic/errors"
"restic/repository"
"github.com/restic/restic/internal/backend"
"github.com/restic/restic/internal/errors"
"github.com/restic/restic/internal/repository"
"github.com/restic/restic/internal/restic"
)
var cmdCat = &cobra.Command{
Use: "cat [flags] [pack|blob|snapshot|index|key|masterkey|config|lock] ID",
Short: "print internal objects to stdout",
Short: "Print internal objects to stdout",
Long: `
The "cat" command is used to print internal objects to stdout.
`,
DisableAutoGenTag: true,
RunE: func(cmd *cobra.Command, args []string) error {
return runCat(globalOptions, args)
},

View File

@@ -8,18 +8,22 @@ import (
"github.com/spf13/cobra"
"restic"
"restic/checker"
"restic/errors"
"github.com/restic/restic/internal/checker"
"github.com/restic/restic/internal/errors"
"github.com/restic/restic/internal/restic"
)
var cmdCheck = &cobra.Command{
Use: "check [flags]",
Short: "check the repository for errors",
Short: "Check the repository for errors",
Long: `
The "check" command tests the repository for errors and reports any errors it
finds. It can also be used to read all data and therefore simulate a restore.
By default, the "check" command will always load all data directly from the
repository and not use a local cache.
`,
DisableAutoGenTag: true,
RunE: func(cmd *cobra.Command, args []string) error {
return runCheck(checkOptions, globalOptions, args)
},
@@ -29,6 +33,7 @@ finds. It can also be used to read all data and therefore simulate a restore.
type CheckOptions struct {
ReadData bool
CheckUnused bool
WithCache bool
}
var checkOptions CheckOptions
@@ -39,6 +44,7 @@ func init() {
f := cmdCheck.Flags()
f.BoolVar(&checkOptions.ReadData, "read-data", false, "read all data blobs")
f.BoolVar(&checkOptions.CheckUnused, "check-unused", false, "find unused blobs")
f.BoolVar(&checkOptions.WithCache, "with-cache", false, "use the cache")
}
func newReadProgress(gopts GlobalOptions, todo restic.Stat) *restic.Progress {
@@ -76,13 +82,18 @@ func runCheck(opts CheckOptions, gopts GlobalOptions, args []string) error {
return errors.Fatal("check has no arguments")
}
if !opts.WithCache {
// do not use a cache for the checker
gopts.NoCache = true
}
repo, err := OpenRepository(gopts)
if err != nil {
return err
}
if !gopts.NoLock {
Verbosef("Create exclusive lock for repository\n")
Verbosef("create exclusive lock for repository\n")
lock, err := lockRepoExclusive(repo)
defer unlockRepo(lock)
if err != nil {
@@ -92,7 +103,7 @@ func runCheck(opts CheckOptions, gopts GlobalOptions, args []string) error {
chkr := checker.New(repo)
Verbosef("Load indexes\n")
Verbosef("load indexes\n")
hints, errs := chkr.LoadIndex(context.TODO())
dupFound := false
@@ -117,7 +128,7 @@ func runCheck(opts CheckOptions, gopts GlobalOptions, args []string) error {
errorsFound := false
errChan := make(chan error)
Verbosef("Check all packs\n")
Verbosef("check all packs\n")
go chkr.Packs(context.TODO(), errChan)
for err := range errChan {
@@ -125,7 +136,7 @@ func runCheck(opts CheckOptions, gopts GlobalOptions, args []string) error {
fmt.Fprintf(os.Stderr, "%v\n", err)
}
Verbosef("Check snapshots, trees and blobs\n")
Verbosef("check snapshots, trees and blobs\n")
errChan = make(chan error)
go chkr.Structure(context.TODO(), errChan)
@@ -149,7 +160,7 @@ func runCheck(opts CheckOptions, gopts GlobalOptions, args []string) error {
}
if opts.ReadData {
Verbosef("Read all data\n")
Verbosef("read all data\n")
p := newReadProgress(gopts, restic.Stat{Blobs: chkr.CountPacks()})
errChan := make(chan error)
@@ -165,5 +176,8 @@ func runCheck(opts CheckOptions, gopts GlobalOptions, args []string) error {
if errorsFound {
return errors.Fatal("repository contains errors")
}
Verbosef("no errors were found\n")
return nil
}

217
cmd/restic/cmd_debug.go Normal file
View File

@@ -0,0 +1,217 @@
// +build debug
package main
import (
"context"
"encoding/json"
"fmt"
"io"
"os"
"github.com/spf13/cobra"
"github.com/restic/restic/internal/errors"
"github.com/restic/restic/internal/pack"
"github.com/restic/restic/internal/repository"
"github.com/restic/restic/internal/restic"
"github.com/restic/restic/internal/worker"
)
var cmdDebug = &cobra.Command{
Use: "debug",
Short: "Debug commands",
}
var cmdDebugDump = &cobra.Command{
Use: "dump [indexes|snapshots|all|packs]",
Short: "Dump data structures",
Long: `
The "dump" command dumps data structures from the repository as JSON objects. It
is used for debugging purposes only.`,
DisableAutoGenTag: true,
RunE: func(cmd *cobra.Command, args []string) error {
return runDebugDump(globalOptions, args)
},
}
func init() {
cmdRoot.AddCommand(cmdDebug)
cmdDebug.AddCommand(cmdDebugDump)
}
func prettyPrintJSON(wr io.Writer, item interface{}) error {
buf, err := json.MarshalIndent(item, "", " ")
if err != nil {
return err
}
_, err = wr.Write(append(buf, '\n'))
return err
}
func debugPrintSnapshots(repo *repository.Repository, wr io.Writer) error {
for id := range repo.List(context.TODO(), restic.SnapshotFile) {
snapshot, err := restic.LoadSnapshot(context.TODO(), repo, id)
if err != nil {
fmt.Fprintf(os.Stderr, "LoadSnapshot(%v): %v", id.Str(), err)
continue
}
fmt.Fprintf(wr, "snapshot_id: %v\n", id)
err = prettyPrintJSON(wr, snapshot)
if err != nil {
return err
}
}
return nil
}
const dumpPackWorkers = 10
// Pack is the struct used in printPacks.
type Pack struct {
Name string `json:"name"`
Blobs []Blob `json:"blobs"`
}
// Blob is the struct used in printPacks.
type Blob struct {
Type restic.BlobType `json:"type"`
Length uint `json:"length"`
ID restic.ID `json:"id"`
Offset uint `json:"offset"`
}
func printPacks(repo *repository.Repository, wr io.Writer) error {
f := func(ctx context.Context, job worker.Job) (interface{}, error) {
name := job.Data.(string)
h := restic.Handle{Type: restic.DataFile, Name: name}
blobInfo, err := repo.Backend().Stat(ctx, h)
if err != nil {
return nil, err
}
blobs, err := pack.List(repo.Key(), restic.ReaderAt(repo.Backend(), h), blobInfo.Size)
if err != nil {
return nil, err
}
return blobs, nil
}
jobCh := make(chan worker.Job)
resCh := make(chan worker.Job)
wp := worker.New(context.TODO(), dumpPackWorkers, f, jobCh, resCh)
go func() {
for name := range repo.Backend().List(context.TODO(), restic.DataFile) {
jobCh <- worker.Job{Data: name}
}
close(jobCh)
}()
for job := range resCh {
name := job.Data.(string)
if job.Error != nil {
fmt.Fprintf(os.Stderr, "error for pack %v: %v\n", name, job.Error)
continue
}
entries := job.Result.([]restic.Blob)
p := Pack{
Name: name,
Blobs: make([]Blob, len(entries)),
}
for i, blob := range entries {
p.Blobs[i] = Blob{
Type: blob.Type,
Length: blob.Length,
ID: blob.ID,
Offset: blob.Offset,
}
}
prettyPrintJSON(os.Stdout, p)
}
wp.Wait()
return nil
}
func dumpIndexes(repo restic.Repository) error {
for id := range repo.List(context.TODO(), restic.IndexFile) {
fmt.Printf("index_id: %v\n", id)
idx, err := repository.LoadIndex(context.TODO(), repo, id)
if err != nil {
return err
}
err = idx.Dump(os.Stdout)
if err != nil {
return err
}
}
return nil
}
func runDebugDump(gopts GlobalOptions, args []string) error {
if len(args) != 1 {
return errors.Fatal("type not specified")
}
repo, err := OpenRepository(gopts)
if err != nil {
return err
}
if !gopts.NoLock {
lock, err := lockRepo(repo)
defer unlockRepo(lock)
if err != nil {
return err
}
}
err = repo.LoadIndex(context.TODO())
if err != nil {
return err
}
tpe := args[0]
switch tpe {
case "indexes":
return dumpIndexes(repo)
case "snapshots":
return debugPrintSnapshots(repo, os.Stdout)
case "packs":
return printPacks(repo, os.Stdout)
case "all":
fmt.Printf("snapshots:\n")
err := debugPrintSnapshots(repo, os.Stdout)
if err != nil {
return err
}
fmt.Printf("\nindexes:\n")
err = dumpIndexes(repo)
if err != nil {
return err
}
return nil
default:
return errors.Fatalf("no such type %q", tpe)
}
}

181
cmd/restic/cmd_dump.go Normal file
View File

@@ -0,0 +1,181 @@
package main
import (
"context"
"fmt"
"os"
"path/filepath"
"github.com/restic/restic/internal/debug"
"github.com/restic/restic/internal/errors"
"github.com/restic/restic/internal/restic"
"github.com/spf13/cobra"
)
var cmdDump = &cobra.Command{
Use: "dump [flags] snapshotID file",
Short: "Print a backed-up file to stdout",
Long: `
The "dump" command extracts a single file from a snapshot from the repository and
prints its contents to stdout.
The special snapshot "latest" can be used to use the latest snapshot in the
repository.
`,
DisableAutoGenTag: true,
RunE: func(cmd *cobra.Command, args []string) error {
return runDump(dumpOptions, globalOptions, args)
},
}
// DumpOptions collects all options for the dump command.
type DumpOptions struct {
Host string
Paths []string
Tags restic.TagLists
}
var dumpOptions DumpOptions
func init() {
cmdRoot.AddCommand(cmdDump)
flags := cmdDump.Flags()
flags.StringVarP(&dumpOptions.Host, "host", "H", "", `only consider snapshots for this host when the snapshot ID is "latest"`)
flags.Var(&dumpOptions.Tags, "tag", "only consider snapshots which include this `taglist` for snapshot ID \"latest\"")
flags.StringArrayVar(&dumpOptions.Paths, "path", nil, "only consider snapshots which include this (absolute) `path` for snapshot ID \"latest\"")
}
func splitPath(path string) []string {
d, f := filepath.Split(path)
if d == "" || d == "/" {
return []string{f}
}
s := splitPath(filepath.Clean(d))
return append(s, f)
}
func dumpNode(ctx context.Context, repo restic.Repository, node *restic.Node) error {
var buf []byte
for _, id := range node.Content {
size, err := repo.LookupBlobSize(id, restic.DataBlob)
if err != nil {
return err
}
buf = buf[:cap(buf)]
if len(buf) < restic.CiphertextLength(int(size)) {
buf = restic.NewBlobBuffer(int(size))
}
n, err := repo.LoadBlob(ctx, restic.DataBlob, id, buf)
if err != nil {
return err
}
buf = buf[:n]
_, err = os.Stdout.Write(buf)
if err != nil {
return errors.Wrap(err, "Write")
}
}
return nil
}
func printFromTree(ctx context.Context, tree *restic.Tree, repo restic.Repository, prefix string, pathComponents []string) error {
if tree == nil {
return fmt.Errorf("called with a nil tree")
}
if repo == nil {
return fmt.Errorf("called with a nil repository")
}
l := len(pathComponents)
if l == 0 {
return fmt.Errorf("empty path components")
}
item := filepath.Join(prefix, pathComponents[0])
for _, node := range tree.Nodes {
if node.Name == pathComponents[0] {
switch {
case l == 1 && node.Type == "file":
return dumpNode(ctx, repo, node)
case l > 1 && node.Type == "dir":
subtree, err := repo.LoadTree(ctx, *node.Subtree)
if err != nil {
return errors.Wrapf(err, "cannot load subtree for %q", item)
}
return printFromTree(ctx, subtree, repo, item, pathComponents[1:])
case l > 1:
return fmt.Errorf("%q should be a dir, but s a %q", item, node.Type)
case node.Type != "file":
return fmt.Errorf("%q should be a file, but is a %q", item, node.Type)
}
}
}
return fmt.Errorf("path %q not found in snapshot", item)
}
func runDump(opts DumpOptions, gopts GlobalOptions, args []string) error {
ctx := gopts.ctx
if len(args) != 2 {
return errors.Fatal("no file and no snapshot ID specified")
}
snapshotIDString := args[0]
pathToPrint := args[1]
debug.Log("dump file %q from %q", pathToPrint, snapshotIDString)
splittedPath := splitPath(pathToPrint)
repo, err := OpenRepository(gopts)
if err != nil {
return err
}
if !gopts.NoLock {
lock, err := lockRepo(repo)
defer unlockRepo(lock)
if err != nil {
return err
}
}
err = repo.LoadIndex(ctx)
if err != nil {
return err
}
var id restic.ID
if snapshotIDString == "latest" {
id, err = restic.FindLatestSnapshot(ctx, repo, opts.Paths, opts.Tags, opts.Host)
if err != nil {
Exitf(1, "latest snapshot for criteria not found: %v Paths:%v Host:%v", err, opts.Paths, opts.Host)
}
} else {
id, err = restic.FindSnapshot(repo, snapshotIDString)
if err != nil {
Exitf(1, "invalid id %q: %v", snapshotIDString, err)
}
}
sn, err := restic.LoadSnapshot(context.TODO(), repo, id)
if err != nil {
Exitf(2, "loading snapshot %q failed: %v", snapshotIDString, err)
}
tree, err := repo.LoadTree(ctx, *sn.Tree)
if err != nil {
Exitf(2, "loading tree for snapshot %q failed: %v", snapshotIDString, err)
}
err = printFromTree(ctx, tree, repo, "", splittedPath)
if err != nil {
Exitf(2, "cannot dump file: %v", err)
}
return nil
}

View File

@@ -9,17 +9,18 @@ import (
"github.com/spf13/cobra"
"restic"
"restic/debug"
"restic/errors"
"github.com/restic/restic/internal/debug"
"github.com/restic/restic/internal/errors"
"github.com/restic/restic/internal/restic"
)
var cmdFind = &cobra.Command{
Use: "find [flags] PATTERN",
Short: "find a file or directory",
Short: "Find a file or directory",
Long: `
The "find" command searches for files or directories in snapshots stored in the
repo. `,
DisableAutoGenTag: true,
RunE: func(cmd *cobra.Command, args []string) error {
return runFind(findOptions, globalOptions, args)
},
@@ -34,7 +35,7 @@ type FindOptions struct {
ListLong bool
Host string
Paths []string
Tags []string
Tags restic.TagLists
}
var findOptions FindOptions
@@ -45,13 +46,13 @@ func init() {
f := cmdFind.Flags()
f.StringVarP(&findOptions.Oldest, "oldest", "O", "", "oldest modification date/time")
f.StringVarP(&findOptions.Newest, "newest", "N", "", "newest modification date/time")
f.StringSliceVarP(&findOptions.Snapshots, "snapshot", "s", nil, "snapshot `id` to search in (can be given multiple times)")
f.StringArrayVarP(&findOptions.Snapshots, "snapshot", "s", nil, "snapshot `id` to search in (can be given multiple times)")
f.BoolVarP(&findOptions.CaseInsensitive, "ignore-case", "i", false, "ignore case for pattern")
f.BoolVarP(&findOptions.ListLong, "long", "l", false, "use a long listing format showing size and mode")
f.StringVarP(&findOptions.Host, "host", "H", "", "only consider snapshots for this `host`, when no snapshot ID is given")
f.StringSliceVar(&findOptions.Tags, "tag", nil, "only consider snapshots which include this `tag`, when no snapshot-ID is given")
f.StringSliceVar(&findOptions.Paths, "path", nil, "only consider snapshots which include this (absolute) `path`, when no snapshot-ID is given")
f.Var(&findOptions.Tags, "tag", "only consider snapshots which include this `taglist`, when no snapshot-ID is given")
f.StringArrayVar(&findOptions.Paths, "path", nil, "only consider snapshots which include this (absolute) `path`, when no snapshot-ID is given")
}
type findPattern struct {

242
cmd/restic/cmd_forget.go Normal file
View File

@@ -0,0 +1,242 @@
package main
import (
"context"
"encoding/json"
"sort"
"strings"
"github.com/restic/restic/internal/errors"
"github.com/restic/restic/internal/restic"
"github.com/spf13/cobra"
)
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. 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 this (now unreferenced)
data after 'forget' was run successfully, see the 'prune' command. `,
DisableAutoGenTag: true,
RunE: func(cmd *cobra.Command, args []string) error {
return runForget(forgetOptions, globalOptions, args)
},
}
// ForgetOptions collects all options for the forget command.
type ForgetOptions struct {
Last int
Hourly int
Daily int
Weekly int
Monthly int
Yearly int
KeepTags restic.TagLists
Host string
Tags restic.TagLists
Paths []string
Compact bool
// Grouping
GroupBy string
DryRun bool
Prune bool
}
var forgetOptions ForgetOptions
func init() {
cmdRoot.AddCommand(cmdForget)
f := cmdForget.Flags()
f.IntVarP(&forgetOptions.Last, "keep-last", "l", 0, "keep the last `n` snapshots")
f.IntVarP(&forgetOptions.Hourly, "keep-hourly", "H", 0, "keep the last `n` hourly snapshots")
f.IntVarP(&forgetOptions.Daily, "keep-daily", "d", 0, "keep the last `n` daily snapshots")
f.IntVarP(&forgetOptions.Weekly, "keep-weekly", "w", 0, "keep the last `n` weekly snapshots")
f.IntVarP(&forgetOptions.Monthly, "keep-monthly", "m", 0, "keep the last `n` monthly snapshots")
f.IntVarP(&forgetOptions.Yearly, "keep-yearly", "y", 0, "keep the last `n` yearly snapshots")
f.Var(&forgetOptions.KeepTags, "keep-tag", "keep snapshots with this `taglist` (can be specified multiple times)")
// Sadly the commonly used shortcut `H` is already used.
f.StringVar(&forgetOptions.Host, "host", "", "only consider snapshots with the given `host`")
// Deprecated since 2017-03-07.
f.StringVar(&forgetOptions.Host, "hostname", "", "only consider snapshots with the given `hostname` (deprecated)")
f.Var(&forgetOptions.Tags, "tag", "only consider snapshots which include this `taglist` in the format `tag[,tag,...]` (can be specified multiple times)")
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 format")
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")
f.SortFlags = false
}
func runForget(opts ForgetOptions, gopts GlobalOptions, args []string) error {
repo, err := OpenRepository(gopts)
if err != nil {
return err
}
lock, err := lockRepoExclusive(repo)
defer unlockRepo(lock)
if err != nil {
return err
}
// group by hostname and dirs
type key struct {
Hostname string
Paths []string
Tags []string
}
snapshotGroups := make(map[string]restic.Snapshots)
var GroupByTag bool
var GroupByHost bool
var GroupByPath bool
var GroupOptionList []string
GroupOptionList = strings.Split(opts.GroupBy, ",")
for _, option := range GroupOptionList {
switch option {
case "host":
GroupByHost = true
case "paths":
GroupByPath = true
case "tags":
GroupByTag = true
case "":
default:
return errors.Fatal("unknown grouping option: '" + option + "'")
}
}
removeSnapshots := 0
ctx, cancel := context.WithCancel(gopts.ctx)
defer cancel()
for sn := range FindFilteredSnapshots(ctx, repo, opts.Host, opts.Tags, opts.Paths, args) {
if len(args) > 0 {
// When explicit snapshots args are given, remove them immediately.
if !opts.DryRun {
h := restic.Handle{Type: restic.SnapshotFile, Name: sn.ID().String()}
if err = repo.Backend().Remove(context.TODO(), h); err != nil {
return err
}
Verbosef("removed snapshot %v\n", sn.ID().Str())
removeSnapshots++
} else {
Verbosef("would have removed snapshot %v\n", sn.ID().Str())
}
} else {
// Determining grouping-keys
var tags []string
var hostname string
var paths []string
if GroupByTag {
tags = sn.Tags
sort.StringSlice(tags).Sort()
}
if GroupByHost {
hostname = sn.Hostname
}
if GroupByPath {
paths = sn.Paths
}
sort.StringSlice(sn.Paths).Sort()
var k []byte
var err error
k, err = json.Marshal(key{Tags: tags, Hostname: hostname, Paths: paths})
if err != nil {
return err
}
snapshotGroups[string(k)] = append(snapshotGroups[string(k)], sn)
}
}
if len(args) > 0 {
return nil
}
policy := restic.ExpirePolicy{
Last: opts.Last,
Hourly: opts.Hourly,
Daily: opts.Daily,
Weekly: opts.Weekly,
Monthly: opts.Monthly,
Yearly: opts.Yearly,
Tags: opts.KeepTags,
}
if policy.Empty() {
Verbosef("no policy was specified, no snapshots will be removed\n")
return nil
}
for k, snapshotGroup := range snapshotGroups {
var key key
if json.Unmarshal([]byte(k), &key) != nil {
return err
}
// Info
Verbosef("snapshots")
var infoStrings []string
if GroupByTag {
infoStrings = append(infoStrings, "tags ["+strings.Join(key.Tags, ", ")+"]")
}
if GroupByHost {
infoStrings = append(infoStrings, "host ["+key.Hostname+"]")
}
if GroupByPath {
infoStrings = append(infoStrings, "paths ["+strings.Join(key.Paths, ", ")+"]")
}
if infoStrings != nil {
Verbosef(" for (" + strings.Join(infoStrings, ", ") + ")")
}
Verbosef(":\n\n")
keep, remove := restic.ApplyPolicy(snapshotGroup, policy)
if len(keep) != 0 && !gopts.Quiet {
Printf("keep %d snapshots:\n", len(keep))
PrintSnapshots(globalOptions.stdout, keep, opts.Compact)
Printf("\n")
}
if len(remove) != 0 && !gopts.Quiet {
Printf("remove %d snapshots:\n", len(remove))
PrintSnapshots(globalOptions.stdout, remove, opts.Compact)
Printf("\n")
}
removeSnapshots += len(remove)
if !opts.DryRun {
for _, sn := range remove {
h := restic.Handle{Type: restic.SnapshotFile, Name: sn.ID().String()}
err = repo.Backend().Remove(context.TODO(), h)
if err != nil {
return err
}
}
}
}
if removeSnapshots > 0 && opts.Prune {
Verbosef("%d snapshots have been removed, running prune\n", removeSnapshots)
if !opts.DryRun {
return pruneRepository(gopts, repo)
}
}
return nil
}

View File

@@ -0,0 +1,94 @@
package main
import (
"time"
"github.com/restic/restic/internal/errors"
"github.com/spf13/cobra"
"github.com/spf13/cobra/doc"
)
var cmdGenerate = &cobra.Command{
Use: "generate [command]",
Short: "Generate manual pages and auto-completion files (bash, zsh)",
Long: `
The "generate" command writes automatically generated files like the man pages
and the auto-completion files for bash and zsh).
`,
DisableAutoGenTag: true,
RunE: runGenerate,
}
type generateOptions struct {
ManDir string
BashCompletionFile string
ZSHCompletionFile string
}
var genOpts generateOptions
func init() {
cmdRoot.AddCommand(cmdGenerate)
fs := cmdGenerate.Flags()
fs.StringVar(&genOpts.ManDir, "man", "", "write man pages to `directory`")
fs.StringVar(&genOpts.BashCompletionFile, "bash-completion", "", "write bash completion `file`")
fs.StringVar(&genOpts.ZSHCompletionFile, "zsh-completion", "", "write zsh completion `file`")
}
func writeManpages(dir string) error {
// use a fixed date for the man pages so that generating them is deterministic
date, err := time.Parse("Jan 2006", "Jan 2017")
if err != nil {
return err
}
header := &doc.GenManHeader{
Title: "restic backup",
Section: "1",
Source: "generated by `restic generate`",
Date: &date,
}
Verbosef("writing man pages to directory %v\n", dir)
return doc.GenManTree(cmdRoot, header, dir)
}
func writeBashCompletion(file string) error {
Verbosef("writing bash completion file to %v\n", file)
return cmdRoot.GenBashCompletionFile(file)
}
func writeZSHCompletion(file string) error {
Verbosef("writing zsh completion file to %v\n", file)
return cmdRoot.GenZshCompletionFile(file)
}
func runGenerate(cmd *cobra.Command, args []string) error {
if genOpts.ManDir != "" {
err := writeManpages(genOpts.ManDir)
if err != nil {
return err
}
}
if genOpts.BashCompletionFile != "" {
err := writeBashCompletion(genOpts.BashCompletionFile)
if err != nil {
return err
}
}
if genOpts.ZSHCompletionFile != "" {
err := writeZSHCompletion(genOpts.ZSHCompletionFile)
if err != nil {
return err
}
}
var empty generateOptions
if genOpts == empty {
return errors.Fatal("nothing to do, please specify at least one output file/dir")
}
return nil
}

View File

@@ -2,18 +2,20 @@ package main
import (
"context"
"restic/errors"
"restic/repository"
"github.com/restic/restic/internal/errors"
"github.com/restic/restic/internal/repository"
"github.com/spf13/cobra"
)
var cmdInit = &cobra.Command{
Use: "init",
Short: "initialize a new repository",
Short: "Initialize a new repository",
Long: `
The "init" command initializes a new repository.
`,
DisableAutoGenTag: true,
RunE: func(cmd *cobra.Command, args []string) error {
return runInit(globalOptions, args)
},
@@ -33,13 +35,11 @@ func runInit(gopts GlobalOptions, args []string) error {
return errors.Fatalf("create backend at %s failed: %v\n", gopts.Repo, err)
}
if gopts.password == "" {
gopts.password, err = ReadPasswordTwice(gopts,
"enter password for new backend: ",
"enter password again: ")
if err != nil {
return err
}
gopts.password, err = ReadPasswordTwice(gopts,
"enter password for new backend: ",
"enter password again: ")
if err != nil {
return err
}
s := repository.New(be)

View File

@@ -3,19 +3,21 @@ package main
import (
"context"
"fmt"
"restic"
"restic/errors"
"restic/repository"
"github.com/restic/restic/internal/errors"
"github.com/restic/restic/internal/repository"
"github.com/restic/restic/internal/restic"
"github.com/spf13/cobra"
)
var cmdKey = &cobra.Command{
Use: "key [list|add|rm|passwd] [ID]",
Short: "manage keys (passwords)",
Use: "key [list|add|remove|passwd] [ID]",
Short: "Manage keys (passwords)",
Long: `
The "key" command manages keys (passwords) for accessing the repository.
`,
DisableAutoGenTag: true,
RunE: func(cmd *cobra.Command, args []string) error {
return runKey(globalOptions, args)
},
@@ -58,7 +60,12 @@ func getNewPassword(gopts GlobalOptions) (string, error) {
return testKeyNewPassword, nil
}
return ReadPasswordTwice(gopts,
// Since we already have an open repository, temporary remove the password
// to prompt the user for the passwd.
newopts := gopts
newopts.password = ""
return ReadPasswordTwice(newopts,
"enter password for new key: ",
"enter password again: ")
}
@@ -117,7 +124,7 @@ func changePassword(gopts GlobalOptions, repo *repository.Repository) error {
}
func runKey(gopts GlobalOptions, args []string) error {
if len(args) < 1 || (args[0] == "rm" && len(args) != 2) || (args[0] != "rm" && len(args) != 1) {
if len(args) < 1 || (args[0] == "remove" && len(args) != 2) || (args[0] != "remove" && len(args) != 1) {
return errors.Fatal("wrong number of arguments")
}
@@ -146,7 +153,7 @@ func runKey(gopts GlobalOptions, args []string) error {
}
return addKey(gopts, repo)
case "rm":
case "remove":
lock, err := lockRepoExclusive(repo)
defer unlockRepo(lock)
if err != nil {

View File

@@ -3,19 +3,21 @@ package main
import (
"context"
"fmt"
"restic"
"restic/errors"
"restic/index"
"github.com/restic/restic/internal/errors"
"github.com/restic/restic/internal/index"
"github.com/restic/restic/internal/restic"
"github.com/spf13/cobra"
)
var cmdList = &cobra.Command{
Use: "list [blobs|packs|index|snapshots|keys|locks]",
Short: "list objects in the repository",
Short: "List objects in the repository",
Long: `
The "list" command allows listing objects in the repository based on type.
`,
DisableAutoGenTag: true,
RunE: func(cmd *cobra.Command, args []string) error {
return runList(globalOptions, args)
},

View File

@@ -6,19 +6,20 @@ import (
"github.com/spf13/cobra"
"restic"
"restic/errors"
"restic/repository"
"github.com/restic/restic/internal/errors"
"github.com/restic/restic/internal/repository"
"github.com/restic/restic/internal/restic"
)
var cmdLs = &cobra.Command{
Use: "ls [flags] [snapshot-ID ...]",
Short: "list files in a snapshot",
Short: "List files in a snapshot",
Long: `
The "ls" command allows listing files and directories in a snapshot.
The special snapshot-ID "latest" can be used to list files and directories of the latest snapshot in the repository.
`,
DisableAutoGenTag: true,
RunE: func(cmd *cobra.Command, args []string) error {
return runLs(lsOptions, globalOptions, args)
},
@@ -28,7 +29,7 @@ The special snapshot-ID "latest" can be used to list files and directories of th
type LsOptions struct {
ListLong bool
Host string
Tags []string
Tags restic.TagLists
Paths []string
}
@@ -41,8 +42,8 @@ func init() {
flags.BoolVarP(&lsOptions.ListLong, "long", "l", false, "use a long listing format showing size and mode")
flags.StringVarP(&lsOptions.Host, "host", "H", "", "only consider snapshots for this `host`, when no snapshot ID is given")
flags.StringSliceVar(&lsOptions.Tags, "tag", nil, "only consider snapshots which include this `tag`, when no snapshot ID is given")
flags.StringSliceVar(&lsOptions.Paths, "path", nil, "only consider snapshots which include this (absolute) `path`, when no snapshot ID is given")
flags.Var(&lsOptions.Tags, "tag", "only consider snapshots which include this `taglist`, when no snapshot ID is given")
flags.StringArrayVar(&lsOptions.Paths, "path", nil, "only consider snapshots which include this (absolute) `path`, when no snapshot ID is given")
}
func printTree(repo *repository.Repository, id *restic.ID, prefix string) error {

108
cmd/restic/cmd_migrate.go Normal file
View File

@@ -0,0 +1,108 @@
package main
import (
"github.com/restic/restic/internal/migrations"
"github.com/restic/restic/internal/restic"
"github.com/spf13/cobra"
)
var cmdMigrate = &cobra.Command{
Use: "migrate [name]",
Short: "Apply migrations",
Long: `
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.
`,
DisableAutoGenTag: true,
RunE: func(cmd *cobra.Command, args []string) error {
return runMigrate(migrateOptions, globalOptions, args)
},
}
// MigrateOptions bundles all options for the 'check' command.
type MigrateOptions struct {
Force bool
}
var migrateOptions MigrateOptions
func init() {
cmdRoot.AddCommand(cmdMigrate)
f := cmdMigrate.Flags()
f.BoolVarP(&migrateOptions.Force, "force", "f", false, `apply a migration a second time`)
}
func checkMigrations(opts MigrateOptions, gopts GlobalOptions, repo restic.Repository) error {
ctx := gopts.ctx
Printf("available migrations:\n")
for _, m := range migrations.All {
ok, err := m.Check(ctx, repo)
if err != nil {
return err
}
if ok {
Printf(" %v: %v\n", m.Name(), m.Desc())
}
}
return nil
}
func applyMigrations(opts MigrateOptions, gopts GlobalOptions, repo restic.Repository, args []string) error {
ctx := gopts.ctx
var firsterr error
for _, name := range args {
for _, m := range migrations.All {
if m.Name() == name {
ok, err := m.Check(ctx, repo)
if err != nil {
return err
}
if !ok {
if !opts.Force {
Warnf("migration %v cannot be applied: check failed\nIf you want to apply this migration anyway, re-run with option --force\n", m.Name())
continue
}
Warnf("check for migration %v failed, continuing anyway\n", m.Name())
}
Printf("applying migration %v...\n", m.Name())
if err = m.Apply(ctx, repo); err != nil {
Warnf("migration %v failed: %v\n", m.Name(), err)
if firsterr == nil {
firsterr = err
}
continue
}
Printf("migration %v: success\n", m.Name())
}
}
}
return firsterr
}
func runMigrate(opts MigrateOptions, gopts GlobalOptions, args []string) error {
repo, err := OpenRepository(gopts)
if err != nil {
return err
}
lock, err := lockRepoExclusive(repo)
defer unlockRepo(lock)
if err != nil {
return err
}
if len(args) == 0 {
return checkMigrations(opts, gopts, repo)
}
return applyMigrations(opts, gopts, repo, args)
}

View File

@@ -9,11 +9,12 @@ import (
"github.com/spf13/cobra"
"restic/debug"
"restic/errors"
"github.com/restic/restic/internal/debug"
"github.com/restic/restic/internal/errors"
"github.com/restic/restic/internal/restic"
resticfs "restic/fs"
"restic/fuse"
resticfs "github.com/restic/restic/internal/fs"
"github.com/restic/restic/internal/fuse"
systemFuse "bazil.org/fuse"
"bazil.org/fuse/fs"
@@ -21,11 +22,12 @@ import (
var cmdMount = &cobra.Command{
Use: "mount [flags] mountpoint",
Short: "mount the repository",
Short: "Mount the repository",
Long: `
The "mount" command mounts the repository via fuse to a directory. This is a
read-only mount.
`,
DisableAutoGenTag: true,
RunE: func(cmd *cobra.Command, args []string) error {
return runMount(mountOptions, globalOptions, args)
},
@@ -37,7 +39,7 @@ type MountOptions struct {
AllowRoot bool
AllowOther bool
Host string
Tags []string
Tags restic.TagLists
Paths []string
}
@@ -52,8 +54,8 @@ func init() {
mountFlags.BoolVar(&mountOptions.AllowOther, "allow-other", false, "allow other users to access the data in the mounted directory")
mountFlags.StringVarP(&mountOptions.Host, "host", "H", "", `only consider snapshots for this host`)
mountFlags.StringSliceVar(&mountOptions.Tags, "tag", nil, "only consider snapshots which include this `tag`")
mountFlags.StringSliceVar(&mountOptions.Paths, "path", nil, "only consider snapshots which include this (absolute) `path`")
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`")
}
func mount(opts MountOptions, gopts GlobalOptions, mountpoint string) error {
@@ -65,6 +67,12 @@ func mount(opts MountOptions, gopts GlobalOptions, mountpoint string) error {
return err
}
lock, err := lockRepo(repo)
defer unlockRepo(lock)
if err != nil {
return err
}
err = repo.LoadIndex(context.TODO())
if err != nil {
return err

View File

@@ -2,18 +2,20 @@ package main
import (
"fmt"
"restic/options"
"github.com/restic/restic/internal/options"
"github.com/spf13/cobra"
)
var optionsCmd = &cobra.Command{
Use: "options",
Short: "print list of extended options",
Short: "Print list of extended options",
Long: `
The "options" command prints a list of extended options.
`,
Hidden: true,
Hidden: true,
DisableAutoGenTag: true,
Run: func(cmd *cobra.Command, args []string) {
fmt.Printf("All Extended Options:\n")
for _, opt := range options.List() {

View File

@@ -2,23 +2,25 @@ package main
import (
"fmt"
"restic"
"restic/debug"
"restic/errors"
"restic/index"
"restic/repository"
"time"
"github.com/restic/restic/internal/debug"
"github.com/restic/restic/internal/errors"
"github.com/restic/restic/internal/index"
"github.com/restic/restic/internal/repository"
"github.com/restic/restic/internal/restic"
"github.com/spf13/cobra"
)
var cmdPrune = &cobra.Command{
Use: "prune [flags]",
Short: "remove unneeded data from the repository",
Short: "Remove unneeded data from the repository",
Long: `
The "prune" command checks the repository and removes data that is not
referenced and therefore not needed any more.
`,
DisableAutoGenTag: true,
RunE: func(cmd *cobra.Command, args []string) error {
return runPrune(globalOptions)
},
@@ -83,6 +85,25 @@ func runPrune(gopts GlobalOptions) error {
return pruneRepository(gopts, repo)
}
func mixedBlobs(list []restic.Blob) bool {
var tree, data bool
for _, pb := range list {
switch pb.Type {
case restic.TreeBlob:
tree = true
case restic.DataBlob:
data = true
}
if tree && data {
return true
}
}
return false
}
func pruneRepository(gopts GlobalOptions, repo restic.Repository) error {
ctx := gopts.ctx
@@ -120,7 +141,7 @@ func pruneRepository(gopts GlobalOptions, repo restic.Repository) error {
stats.bytes += pack.Size
blobs += len(pack.Entries)
}
Verbosef("repository contains %v packs (%v blobs) with %v bytes\n",
Verbosef("repository contains %v packs (%v blobs) with %v\n",
len(idx.Packs), blobs, formatBytes(uint64(stats.bytes)))
blobCount := make(map[restic.BlobHandle]int)
@@ -177,12 +198,23 @@ func pruneRepository(gopts GlobalOptions, repo restic.Repository) error {
}
bar.Done()
if len(usedBlobs) > stats.blobs {
return errors.Fatalf("number of used blobs is larger than number of available blobs!\n" +
"Please report this error (along with the output of the 'prune' run) at\n" +
"https://github.com/restic/restic/issues/new")
}
Verbosef("found %d of %d data blobs still in use, removing %d blobs\n",
len(usedBlobs), stats.blobs, stats.blobs-len(usedBlobs))
// find packs that need a rewrite
rewritePacks := restic.NewIDSet()
for _, pack := range idx.Packs {
if mixedBlobs(pack.Entries) {
rewritePacks.Insert(pack.ID)
continue
}
for _, blob := range pack.Entries {
h := restic.BlobHandle{ID: blob.ID, Type: blob.Type}
if !usedBlobs.Has(h) {
@@ -235,22 +267,23 @@ func pruneRepository(gopts GlobalOptions, repo restic.Repository) error {
Verbosef("will delete %d packs and rewrite %d packs, this frees %s\n",
len(removePacks), len(rewritePacks), formatBytes(uint64(removeBytes)))
var repackedBlobs restic.IDSet
var obsoletePacks restic.IDSet
if len(rewritePacks) != 0 {
bar = newProgressMax(!gopts.Quiet, uint64(len(rewritePacks)), "packs rewritten")
bar.Start()
repackedBlobs, err = repository.Repack(ctx, repo, rewritePacks, usedBlobs, bar)
obsoletePacks, err = repository.Repack(ctx, repo, rewritePacks, usedBlobs, bar)
if err != nil {
return err
}
bar.Done()
}
removePacks.Merge(obsoletePacks)
if err = rebuildIndex(ctx, repo, removePacks); err != nil {
return err
}
removePacks.Merge(repackedBlobs)
if len(removePacks) != 0 {
bar = newProgressMax(!gopts.Quiet, uint64(len(removePacks)), "packs deleted")
bar.Start()

View File

@@ -2,19 +2,21 @@ package main
import (
"context"
"restic"
"restic/index"
"github.com/restic/restic/internal/index"
"github.com/restic/restic/internal/restic"
"github.com/spf13/cobra"
)
var cmdRebuildIndex = &cobra.Command{
Use: "rebuild-index [flags]",
Short: "build a new index file",
Short: "Build a new index file",
Long: `
The "rebuild-index" command creates a new index based on the pack files in the
repository.
`,
DisableAutoGenTag: true,
RunE: func(cmd *cobra.Command, args []string) error {
return runRebuildIndex(globalOptions)
},

157
cmd/restic/cmd_restore.go Normal file
View File

@@ -0,0 +1,157 @@
package main
import (
"github.com/restic/restic/internal/debug"
"github.com/restic/restic/internal/errors"
"github.com/restic/restic/internal/filter"
"github.com/restic/restic/internal/restic"
"github.com/spf13/cobra"
)
var cmdRestore = &cobra.Command{
Use: "restore [flags] snapshotID",
Short: "Extract the data from a snapshot",
Long: `
The "restore" command extracts the data from a snapshot from the repository to
a directory.
The special snapshot "latest" can be used to restore the latest snapshot in the
repository.
`,
DisableAutoGenTag: true,
RunE: func(cmd *cobra.Command, args []string) error {
return runRestore(restoreOptions, globalOptions, args)
},
}
// RestoreOptions collects all options for the restore command.
type RestoreOptions struct {
Exclude []string
Include []string
Target string
Host string
Paths []string
Tags restic.TagLists
}
var restoreOptions RestoreOptions
func init() {
cmdRoot.AddCommand(cmdRestore)
flags := cmdRestore.Flags()
flags.StringArrayVarP(&restoreOptions.Exclude, "exclude", "e", nil, "exclude a `pattern` (can be specified multiple times)")
flags.StringArrayVarP(&restoreOptions.Include, "include", "i", nil, "include a `pattern`, exclude everything else (can be specified multiple times)")
flags.StringVarP(&restoreOptions.Target, "target", "t", "", "directory to extract data to")
flags.StringVarP(&restoreOptions.Host, "host", "H", "", `only consider snapshots for this host when the snapshot ID is "latest"`)
flags.Var(&restoreOptions.Tags, "tag", "only consider snapshots which include this `taglist` for snapshot ID \"latest\"")
flags.StringArrayVar(&restoreOptions.Paths, "path", nil, "only consider snapshots which include this (absolute) `path` for snapshot ID \"latest\"")
}
func runRestore(opts RestoreOptions, gopts GlobalOptions, args []string) error {
ctx := gopts.ctx
if len(args) != 1 {
return errors.Fatal("no snapshot ID specified")
}
if opts.Target == "" {
return errors.Fatal("please specify a directory to restore to (--target)")
}
if len(opts.Exclude) > 0 && len(opts.Include) > 0 {
return errors.Fatal("exclude and include patterns are mutually exclusive")
}
snapshotIDString := args[0]
debug.Log("restore %v to %v", snapshotIDString, opts.Target)
repo, err := OpenRepository(gopts)
if err != nil {
return err
}
if !gopts.NoLock {
lock, err := lockRepo(repo)
defer unlockRepo(lock)
if err != nil {
return err
}
}
err = repo.LoadIndex(ctx)
if err != nil {
return err
}
var id restic.ID
if snapshotIDString == "latest" {
id, err = restic.FindLatestSnapshot(ctx, repo, opts.Paths, opts.Tags, opts.Host)
if err != nil {
Exitf(1, "latest snapshot for criteria not found: %v Paths:%v Host:%v", err, opts.Paths, opts.Host)
}
} else {
id, err = restic.FindSnapshot(repo, snapshotIDString)
if err != nil {
Exitf(1, "invalid id %q: %v", snapshotIDString, err)
}
}
res, err := restic.NewRestorer(repo, id)
if err != nil {
Exitf(2, "creating restorer failed: %v\n", err)
}
totalErrors := 0
res.Error = func(dir string, node *restic.Node, err error) error {
Warnf("ignoring error for %s: %s\n", dir, err)
totalErrors++
return nil
}
selectExcludeFilter := func(item string, dstpath string, node *restic.Node) (selectedForRestore bool, childMayBeSelected bool) {
matched, _, err := filter.List(opts.Exclude, item)
if err != nil {
Warnf("error for exclude pattern: %v", err)
}
// An exclude filter is basically a 'wildcard but foo',
// so even if a childMayMatch, other children of a dir may not,
// therefore childMayMatch does not matter, but we should not go down
// unless the dir is selected for restore
selectedForRestore = !matched
childMayBeSelected = selectedForRestore && node.Type == "dir"
return selectedForRestore, childMayBeSelected
}
selectIncludeFilter := func(item string, dstpath string, node *restic.Node) (selectedForRestore bool, childMayBeSelected bool) {
matched, childMayMatch, err := filter.List(opts.Include, item)
if err != nil {
Warnf("error for include pattern: %v", err)
}
selectedForRestore = matched
childMayBeSelected = childMayMatch && node.Type == "dir"
return selectedForRestore, childMayBeSelected
}
if len(opts.Exclude) > 0 {
res.SelectFilter = selectExcludeFilter
} else if len(opts.Include) > 0 {
res.SelectFilter = selectIncludeFilter
}
Verbosef("restoring %s to %s\n", res.Snapshot(), opts.Target)
err = res.RestoreTo(ctx, opts.Target)
if totalErrors > 0 {
Printf("There were %d errors\n", totalErrors)
}
return err
}

239
cmd/restic/cmd_snapshots.go Normal file
View File

@@ -0,0 +1,239 @@
package main
import (
"context"
"encoding/json"
"fmt"
"io"
"sort"
"strings"
"github.com/restic/restic/internal/restic"
"github.com/spf13/cobra"
)
var cmdSnapshots = &cobra.Command{
Use: "snapshots [snapshotID ...]",
Short: "List all snapshots",
Long: `
The "snapshots" command lists all snapshots stored in the repository.
`,
DisableAutoGenTag: true,
RunE: func(cmd *cobra.Command, args []string) error {
return runSnapshots(snapshotOptions, globalOptions, args)
},
}
// SnapshotOptions bundles all options for the snapshots command.
type SnapshotOptions struct {
Host string
Tags restic.TagLists
Paths []string
Compact bool
Last bool
}
var snapshotOptions SnapshotOptions
func init() {
cmdRoot.AddCommand(cmdSnapshots)
f := cmdSnapshots.Flags()
f.StringVarP(&snapshotOptions.Host, "host", "H", "", "only consider snapshots for this `host`")
f.Var(&snapshotOptions.Tags, "tag", "only consider snapshots which include this `taglist` (can be specified multiple times)")
f.StringArrayVar(&snapshotOptions.Paths, "path", nil, "only consider snapshots for this `path` (can be specified multiple times)")
f.BoolVarP(&snapshotOptions.Compact, "compact", "c", false, "use compact format")
f.BoolVar(&snapshotOptions.Last, "last", false, "only show the last snapshot for each host and path")
}
func runSnapshots(opts SnapshotOptions, gopts GlobalOptions, args []string) error {
repo, err := OpenRepository(gopts)
if err != nil {
return err
}
if !gopts.NoLock {
lock, err := lockRepo(repo)
defer unlockRepo(lock)
if err != nil {
return err
}
}
ctx, cancel := context.WithCancel(gopts.ctx)
defer cancel()
var list restic.Snapshots
for sn := range FindFilteredSnapshots(ctx, repo, opts.Host, opts.Tags, opts.Paths, args) {
list = append(list, sn)
}
if opts.Last {
list = FilterLastSnapshots(list)
}
sort.Sort(sort.Reverse(list))
if gopts.JSON {
err := printSnapshotsJSON(gopts.stdout, list)
if err != nil {
Warnf("error printing snapshot: %v\n", err)
}
return nil
}
PrintSnapshots(gopts.stdout, list, opts.Compact)
return nil
}
// filterLastSnapshotsKey is used by FilterLastSnapshots.
type filterLastSnapshotsKey struct {
Hostname string
JoinedPaths string
}
// newFilterLastSnapshotsKey initializes a filterLastSnapshotsKey from a Snapshot
func newFilterLastSnapshotsKey(sn *restic.Snapshot) filterLastSnapshotsKey {
// Shallow slice copy
var paths = make([]string, len(sn.Paths))
copy(paths, sn.Paths)
sort.Strings(paths)
return filterLastSnapshotsKey{sn.Hostname, strings.Join(paths, "|")}
}
// FilterLastSnapshots filters a list of snapshots to only return the last
// entry for each hostname and path. If the snapshot contains multiple paths,
// they will be joined and treated as one item.
func FilterLastSnapshots(list restic.Snapshots) restic.Snapshots {
// Sort the snapshots so that the newer ones are listed first
sort.SliceStable(list, func(i, j int) bool {
return list[i].Time.After(list[j].Time)
})
var results restic.Snapshots
seen := make(map[filterLastSnapshotsKey]bool)
for _, sn := range list {
key := newFilterLastSnapshotsKey(sn)
if !seen[key] {
seen[key] = true
results = append(results, sn)
}
}
return results
}
// PrintSnapshots prints a text table of the snapshots in list to stdout.
func PrintSnapshots(stdout io.Writer, list restic.Snapshots, compact bool) {
// always sort the snapshots so that the newer ones are listed last
sort.SliceStable(list, func(i, j int) bool {
return list[i].Time.Before(list[j].Time)
})
// Determine the max widths for host and tag.
maxHost, maxTag := 10, 6
for _, sn := range list {
if len(sn.Hostname) > maxHost {
maxHost = len(sn.Hostname)
}
for _, tag := range sn.Tags {
if len(tag) > maxTag {
maxTag = len(tag)
}
}
}
tab := NewTable()
if !compact {
tab.Header = fmt.Sprintf("%-8s %-19s %-*s %-*s %-3s %s", "ID", "Date", -maxHost, "Host", -maxTag, "Tags", "", "Directory")
tab.RowFormat = fmt.Sprintf("%%-8s %%-19s %%%ds %%%ds %%-3s %%s", -maxHost, -maxTag)
} else {
tab.Header = fmt.Sprintf("%-8s %-19s %-*s %-*s", "ID", "Date", -maxHost, "Host", -maxTag, "Tags")
tab.RowFormat = fmt.Sprintf("%%-8s %%-19s %%%ds %%s", -maxHost)
}
for _, sn := range list {
if len(sn.Paths) == 0 {
continue
}
firstTag := ""
if len(sn.Tags) > 0 {
firstTag = sn.Tags[0]
}
rows := len(sn.Paths)
if rows < len(sn.Tags) {
rows = len(sn.Tags)
}
treeElement := " "
if rows != 1 {
treeElement = "┌──"
}
if !compact {
tab.Rows = append(tab.Rows, []interface{}{sn.ID().Str(), sn.Time.Format(TimeFormat), sn.Hostname, firstTag, treeElement, sn.Paths[0]})
} else {
allTags := ""
for _, tag := range sn.Tags {
allTags += tag + " "
}
tab.Rows = append(tab.Rows, []interface{}{sn.ID().Str(), sn.Time.Format(TimeFormat), sn.Hostname, allTags})
continue
}
if len(sn.Tags) > rows {
rows = len(sn.Tags)
}
for i := 1; i < rows; i++ {
path := ""
if len(sn.Paths) > i {
path = sn.Paths[i]
}
tag := ""
if len(sn.Tags) > i {
tag = sn.Tags[i]
}
treeElement := "│"
if i == (rows - 1) {
treeElement = "└──"
}
tab.Rows = append(tab.Rows, []interface{}{"", "", "", tag, treeElement, path})
}
}
tab.Footer = fmt.Sprintf("%d snapshots", len(list))
tab.Write(stdout)
}
// Snapshot helps to print Snaphots as JSON with their ID included.
type Snapshot struct {
*restic.Snapshot
ID *restic.ID `json:"id"`
ShortID string `json:"short_id"`
}
// printSnapshotsJSON writes the JSON representation of list to stdout.
func printSnapshotsJSON(stdout io.Writer, list restic.Snapshots) error {
var snapshots []Snapshot
for _, sn := range list {
k := Snapshot{
Snapshot: sn,
ID: sn.ID(),
ShortID: sn.ID().Str(),
}
snapshots = append(snapshots, k)
}
return json.NewEncoder(stdout).Encode(snapshots)
}

View File

@@ -5,15 +5,15 @@ import (
"github.com/spf13/cobra"
"restic"
"restic/debug"
"restic/errors"
"restic/repository"
"github.com/restic/restic/internal/debug"
"github.com/restic/restic/internal/errors"
"github.com/restic/restic/internal/repository"
"github.com/restic/restic/internal/restic"
)
var cmdTag = &cobra.Command{
Use: "tag [flags] [snapshot-ID ...]",
Short: "modifies tags on snapshots",
Short: "Modify tags on snapshots",
Long: `
The "tag" command allows you to modify tags on exiting snapshots.
@@ -22,6 +22,7 @@ add tags to/remove tags from the existing set.
When no snapshot-ID is given, all snapshots matching the host, tag and path filter criteria are modified.
`,
DisableAutoGenTag: true,
RunE: func(cmd *cobra.Command, args []string) error {
return runTag(tagOptions, globalOptions, args)
},
@@ -31,7 +32,7 @@ When no snapshot-ID is given, all snapshots matching the host, tag and path filt
type TagOptions struct {
Host string
Paths []string
Tags []string
Tags restic.TagLists
SetTags []string
AddTags []string
RemoveTags []string
@@ -48,8 +49,8 @@ func init() {
tagFlags.StringSliceVar(&tagOptions.RemoveTags, "remove", nil, "`tag` which will be removed from the existing tags (can be given multiple times)")
tagFlags.StringVarP(&tagOptions.Host, "host", "H", "", "only consider snapshots for this `host`, when no snapshot ID is given")
tagFlags.StringSliceVar(&tagOptions.Tags, "tag", nil, "only consider snapshots which include this `tag`, when no snapshot-ID is given")
tagFlags.StringSliceVar(&tagOptions.Paths, "path", nil, "only consider snapshots which include this (absolute) `path`, when no snapshot-ID is given")
tagFlags.Var(&tagOptions.Tags, "tag", "only consider snapshots which include this `taglist`, when no snapshot-ID is given")
tagFlags.StringArrayVar(&tagOptions.Paths, "path", nil, "only consider snapshots which include this (absolute) `path`, when no snapshot-ID is given")
}
func changeTags(repo *repository.Repository, sn *restic.Snapshot, setTags, addTags, removeTags []string) (bool, error) {
@@ -112,7 +113,7 @@ func runTag(opts TagOptions, gopts GlobalOptions, args []string) error {
}
if !gopts.NoLock {
Verbosef("Create exclusive lock for repository\n")
Verbosef("create exclusive lock for repository\n")
lock, err := lockRepoExclusive(repo)
defer unlockRepo(lock)
if err != nil {
@@ -134,9 +135,9 @@ func runTag(opts TagOptions, gopts GlobalOptions, args []string) error {
}
}
if changeCnt == 0 {
Verbosef("No snapshots were modified\n")
Verbosef("no snapshots were modified\n")
} else {
Verbosef("Modified tags on %v snapshots\n", changeCnt)
Verbosef("modified tags on %v snapshots\n", changeCnt)
}
return nil
}

View File

@@ -2,17 +2,18 @@ package main
import (
"context"
"restic"
"github.com/restic/restic/internal/restic"
"github.com/spf13/cobra"
)
var unlockCmd = &cobra.Command{
Use: "unlock",
Short: "remove locks other processes created",
Short: "Remove locks other processes created",
Long: `
The "unlock" command removes stale locks that have been created by other restic processes.
`,
DisableAutoGenTag: true,
RunE: func(cmd *cobra.Command, args []string) error {
return runUnlock(unlockOptions, globalOptions)
},

View File

@@ -9,11 +9,12 @@ import (
var versionCmd = &cobra.Command{
Use: "version",
Short: "print version information",
Short: "Print version information",
Long: `
The "version" command prints detailed information about the build environment
and the version of this software.
`,
DisableAutoGenTag: true,
Run: func(cmd *cobra.Command, args []string) {
fmt.Printf("restic %s\ncompiled with %v on %v/%v\n",
version, runtime.Version(), runtime.GOOS, runtime.GOARCH)

266
cmd/restic/exclude.go Normal file
View File

@@ -0,0 +1,266 @@
package main
import (
"bytes"
"fmt"
"io"
"os"
"path/filepath"
"strings"
"sync"
"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"
)
type rejectionCache struct {
m map[string]bool
mtx sync.Mutex
}
// Lock locks the mutex in rc.
func (rc *rejectionCache) Lock() {
if rc != nil {
rc.mtx.Lock()
}
}
// Unlock unlocks the mutex in rc.
func (rc *rejectionCache) Unlock() {
if rc != nil {
rc.mtx.Unlock()
}
}
// Get returns the last stored value for dir and a second boolean that
// indicates whether that value was actually written to the cache. It is the
// callers responsibility to call rc.Lock and rc.Unlock before using this
// method, otherwise data races may occur.
func (rc *rejectionCache) Get(dir string) (bool, bool) {
if rc == nil || rc.m == nil {
return false, false
}
v, ok := rc.m[dir]
return v, ok
}
// Store stores a new value for dir. It is the callers responsibility to call
// rc.Lock and rc.Unlock before using this method, otherwise data races may
// occur.
func (rc *rejectionCache) Store(dir string, rejected bool) {
if rc == nil {
return
}
if rc.m == nil {
rc.m = make(map[string]bool)
}
rc.m[dir] = rejected
}
// RejectFunc is a function that takes a filename and os.FileInfo of a
// file that would be included in the backup. The function returns true if it
// should be excluded (rejected) from the backup.
type RejectFunc func(path string, fi os.FileInfo) bool
// rejectByPattern returns a RejectFunc which rejects files that match
// one of the patterns.
func rejectByPattern(patterns []string) RejectFunc {
return func(item string, fi os.FileInfo) bool {
matched, _, err := filter.List(patterns, item)
if err != nil {
Warnf("error for exclude pattern: %v", err)
}
if matched {
debug.Log("path %q excluded by an exclude pattern", item)
return true
}
return false
}
}
// rejectIfPresent returns a RejectFunc which itself returns whether a path
// should be excluded. The RejectFunc considers a file to be excluded when
// it resides in a directory with an exclusion file, that is specified by
// excludeFileSpec in the form "filename[:content]". The returned error is
// non-nil if the filename component of excludeFileSpec is empty. If rc is
// non-nil, it is going to be used in the RejectFunc to expedite the evaluation
// of a directory based on previous visits.
func rejectIfPresent(excludeFileSpec string, rc *rejectionCache) (RejectFunc, error) {
if excludeFileSpec == "" {
return nil, errors.New("name for exclusion tagfile is empty")
}
colon := strings.Index(excludeFileSpec, ":")
if colon == 0 {
return nil, fmt.Errorf("no name for exclusion tagfile provided")
}
tf, tc := "", ""
if colon > 0 {
tf = excludeFileSpec[:colon]
tc = excludeFileSpec[colon+1:]
} else {
tf = excludeFileSpec
}
debug.Log("using %q as exclusion tagfile", tf)
fn := func(filename string, _ os.FileInfo) bool {
return isExcludedByFile(filename, tf, tc, rc)
}
return fn, nil
}
// isExcludedByFile interprets filename as a path and returns true if that file
// is in a excluded directory. A directory is identified as excluded if it contains a
// tagfile which bears the name specified in tagFilename and starts with
// header. If rc is non-nil, it is used to expedite the evaluation of a
// directory based on previous visits.
func isExcludedByFile(filename, tagFilename, header string, rc *rejectionCache) bool {
if tagFilename == "" {
return false
}
dir, base := filepath.Split(filename)
if base == tagFilename {
return false // do not exclude the tagfile itself
}
rc.Lock()
defer rc.Unlock()
rejected, visited := rc.Get(dir)
if visited {
return rejected
}
rejected = isDirExcludedByFile(dir, tagFilename, header)
rc.Store(dir, rejected)
return rejected
}
func isDirExcludedByFile(dir, tagFilename, header string) bool {
tf := filepath.Join(dir, tagFilename)
_, err := fs.Lstat(tf)
if os.IsNotExist(err) {
return false
}
if err != nil {
Warnf("could not access exclusion tagfile: %v", err)
return false
}
// when no signature is given, the mere presence of tf is enough reason
// to exclude filename
if len(header) == 0 {
return true
}
// From this stage, errors mean tagFilename exists but it is malformed.
// Warnings will be generated so that the user is informed that the
// indented ignore-action is not performed.
f, err := os.Open(tf)
if err != nil {
Warnf("could not open exclusion tagfile: %v", err)
return false
}
defer f.Close()
buf := make([]byte, len(header))
_, err = io.ReadFull(f, buf)
// EOF is handled with a dedicated message, otherwise the warning were too cryptic
if err == io.EOF {
Warnf("invalid (too short) signature in exclusion tagfile %q\n", tf)
return false
}
if err != nil {
Warnf("could not read signature from exclusion tagfile %q: %v\n", tf, err)
return false
}
if bytes.Compare(buf, []byte(header)) != 0 {
Warnf("invalid signature in exclusion tagfile %q\n", tf)
return false
}
return true
}
// gatherDevices returns the set of unique device ids of the files and/or
// directory paths listed in "items".
func gatherDevices(items []string) (deviceMap map[string]uint64, err error) {
deviceMap = make(map[string]uint64)
for _, item := range items {
fi, err := fs.Lstat(item)
if err != nil {
return nil, err
}
id, err := fs.DeviceID(fi)
if err != nil {
return nil, err
}
deviceMap[item] = id
}
if len(deviceMap) == 0 {
return nil, errors.New("zero allowed devices")
}
return deviceMap, nil
}
// rejectByDevice returns a RejectFunc that rejects files which are on a
// different file systems than the files/dirs in samples.
func rejectByDevice(samples []string) (RejectFunc, error) {
allowed, err := gatherDevices(samples)
if err != nil {
return nil, err
}
debug.Log("allowed devices: %v\n", allowed)
return func(item string, fi os.FileInfo) bool {
if fi == nil {
return false
}
id, err := fs.DeviceID(fi)
if err != nil {
// This should never happen because gatherDevices() would have
// errored out earlier. If it still does that's a reason to panic.
panic(err)
}
for dir := item; dir != ""; dir = filepath.Dir(dir) {
debug.Log("item %v, test dir %v", item, dir)
allowedID, ok := allowed[dir]
if !ok {
continue
}
if allowedID != id {
debug.Log("path %q on disallowed device %d", item, id)
return true
}
return false
}
panic(fmt.Sprintf("item %v, device id %v not found, allowedDevs: %v", item, id, allowed))
}, nil
}
// rejectResticCache returns a RejectFunc that rejects the restic cache
// directory (if set).
func rejectResticCache(repo *repository.Repository) (RejectFunc, error) {
if repo.Cache == nil {
return func(string, os.FileInfo) bool {
return false
}, nil
}
cacheBase := repo.Cache.BaseDir()
if cacheBase == "" {
return nil, errors.New("cacheBase is empty string")
}
return func(item string, _ os.FileInfo) bool {
if fs.HasPathPrefix(cacheBase, item) {
debug.Log("rejecting restic cache directory %v", item)
return true
}
return false
}, nil
}

View File

@@ -0,0 +1,84 @@
package main
import (
"io/ioutil"
"path/filepath"
"testing"
"github.com/restic/restic/internal/test"
)
func TestRejectByPattern(t *testing.T) {
var tests = []struct {
filename string
reject bool
}{
{filename: "/home/user/foo.go", reject: true},
{filename: "/home/user/foo.c", reject: false},
{filename: "/home/user/foobar", reject: false},
{filename: "/home/user/foobar/x", reject: true},
{filename: "/home/user/README", reject: false},
{filename: "/home/user/README.md", reject: true},
}
patterns := []string{"*.go", "README.md", "/home/user/foobar/*"}
for _, tc := range tests {
t.Run("", func(t *testing.T) {
reject := rejectByPattern(patterns)
res := reject(tc.filename, nil)
if res != tc.reject {
t.Fatalf("wrong result for filename %v: want %v, got %v",
tc.filename, tc.reject, res)
}
})
}
}
func TestIsExcludedByFile(t *testing.T) {
const (
tagFilename = "CACHEDIR.TAG"
header = "Signature: 8a477f597d28d172789f06886806bc55"
)
tests := []struct {
name string
tagFile string
content string
want bool
}{
{"NoTagfile", "", "", false},
{"EmptyTagfile", tagFilename, "", true},
{"UnnamedTagFile", "", header, false},
{"WrongTagFile", "notatagfile", header, false},
{"IncorrectSig", tagFilename, header[1:], false},
{"ValidSig", tagFilename, header, true},
{"ValidPlusStuff", tagFilename, header + "foo", true},
{"ValidPlusNewlineAndStuff", tagFilename, header + "\nbar", true},
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
tempDir, cleanup := test.TempDir(t)
defer cleanup()
foo := filepath.Join(tempDir, "foo")
err := ioutil.WriteFile(foo, []byte("foo"), 0666)
if err != nil {
t.Fatalf("could not write file: %v", err)
}
if tc.tagFile != "" {
tagFile := filepath.Join(tempDir, tc.tagFile)
err = ioutil.WriteFile(tagFile, []byte(tc.content), 0666)
if err != nil {
t.Fatalf("could not write tagfile: %v", err)
}
}
h := header
if tc.content == "" {
h = ""
}
if got := isExcludedByFile(foo, tagFilename, h, nil); tc.want != got {
t.Fatalf("expected %v, got %v", tc.want, got)
}
})
}
}

31
cmd/restic/excludes Normal file
View File

@@ -0,0 +1,31 @@
/boot
/dev
/etc
/home
/lost+found
/mnt
/proc
/root
/run
/sys
/tmp
/usr
/var
/opt/android-sdk
/opt/bullet
/opt/dex2jar
/opt/jameica
/opt/google
/opt/JDownloader
/opt/JDownloaderScripts
/opt/opencascade
/opt/vagrant
/opt/visual-studio-code
/opt/vtk6
/bin
/fonts*
/srv/ftp
/srv/http
/sbin
/lib
/lib64

View File

@@ -3,12 +3,12 @@ package main
import (
"context"
"restic"
"restic/repository"
"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, repo *repository.Repository, host string, tags []string, paths []string, snapshotIDs []string) <-chan *restic.Snapshot {
func FindFilteredSnapshots(ctx context.Context, repo *repository.Repository, host string, tags []restic.TagList, paths []string, snapshotIDs []string) <-chan *restic.Snapshot {
out := make(chan *restic.Snapshot)
go func() {
defer close(out)

View File

@@ -6,7 +6,7 @@ import (
"path/filepath"
"time"
"restic"
"github.com/restic/restic/internal/restic"
)
func formatBytes(c uint64) string {

590
cmd/restic/global.go Normal file
View File

@@ -0,0 +1,590 @@
package main
import (
"context"
"fmt"
"io"
"io/ioutil"
"os"
"runtime"
"strings"
"syscall"
"time"
"github.com/restic/restic/internal/backend"
"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/local"
"github.com/restic/restic/internal/backend/location"
"github.com/restic/restic/internal/backend/rest"
"github.com/restic/restic/internal/backend/s3"
"github.com/restic/restic/internal/backend/sftp"
"github.com/restic/restic/internal/backend/swift"
"github.com/restic/restic/internal/cache"
"github.com/restic/restic/internal/debug"
"github.com/restic/restic/internal/limiter"
"github.com/restic/restic/internal/options"
"github.com/restic/restic/internal/repository"
"github.com/restic/restic/internal/restic"
"github.com/restic/restic/internal/errors"
"golang.org/x/crypto/ssh/terminal"
)
var version = "compiled manually"
// GlobalOptions hold all global options for restic.
type GlobalOptions struct {
Repo string
PasswordFile string
Quiet bool
NoLock bool
JSON bool
CacheDir string
NoCache bool
CACerts []string
LimitUploadKb int
LimitDownloadKb int
ctx context.Context
password string
stdout io.Writer
stderr io.Writer
Options []string
extended options.Options
}
var globalOptions = GlobalOptions{
stdout: os.Stdout,
stderr: os.Stderr,
}
func init() {
var cancel context.CancelFunc
globalOptions.ctx, cancel = context.WithCancel(context.Background())
AddCleanupHandler(func() error {
cancel()
return nil
})
f := cmdRoot.PersistentFlags()
f.StringVarP(&globalOptions.Repo, "repo", "r", os.Getenv("RESTIC_REPOSITORY"), "repository to backup to or restore from (default: $RESTIC_REPOSITORY)")
f.StringVarP(&globalOptions.PasswordFile, "password-file", "p", os.Getenv("RESTIC_PASSWORD_FILE"), "read the repository password from a file (default: $RESTIC_PASSWORD_FILE)")
f.BoolVarP(&globalOptions.Quiet, "quiet", "q", false, "do not output comprehensive progress report")
f.BoolVar(&globalOptions.NoLock, "no-lock", false, "do not lock the repo, this allows some operations on read-only repos")
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")
f.BoolVar(&globalOptions.NoCache, "no-cache", false, "do not use a local cache")
f.StringSliceVar(&globalOptions.CACerts, "cacert", nil, "path to load root certificates from (default: use system certificates)")
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)")
restoreTerminal()
}
// checkErrno returns nil when err is set to syscall.Errno(0), since this is no
// error condition.
func checkErrno(err error) error {
e, ok := err.(syscall.Errno)
if !ok {
return err
}
if e == 0 {
return nil
}
return err
}
func stdinIsTerminal() bool {
return terminal.IsTerminal(int(os.Stdin.Fd()))
}
func stdoutIsTerminal() bool {
return terminal.IsTerminal(int(os.Stdout.Fd()))
}
func stdoutTerminalWidth() int {
w, _, err := terminal.GetSize(int(os.Stdout.Fd()))
if err != nil {
return 0
}
return w
}
// restoreTerminal installs a cleanup handler that restores the previous
// terminal state on exit.
func restoreTerminal() {
if !stdoutIsTerminal() {
return
}
fd := int(os.Stdout.Fd())
state, err := terminal.GetState(fd)
if err != nil {
fmt.Fprintf(os.Stderr, "unable to get terminal state: %v\n", err)
return
}
AddCleanupHandler(func() error {
err := checkErrno(terminal.Restore(fd, state))
if err != nil {
fmt.Fprintf(os.Stderr, "unable to get restore terminal state: %#+v\n", err)
}
return err
})
}
// ClearLine creates a platform dependent string to clear the current
// line, so it can be overwritten. ANSI sequences are not supported on
// current windows cmd shell.
func ClearLine() string {
if runtime.GOOS == "windows" {
if w := stdoutTerminalWidth(); w > 0 {
return strings.Repeat(" ", w-1) + "\r"
}
return ""
}
return "\x1b[2K"
}
// Printf writes the message to the configured stdout stream.
func Printf(format string, args ...interface{}) {
_, err := fmt.Fprintf(globalOptions.stdout, format, args...)
if err != nil {
fmt.Fprintf(os.Stderr, "unable to write to stdout: %v\n", err)
Exit(100)
}
}
// Verbosef calls Printf to write the message when the verbose flag is set.
func Verbosef(format string, args ...interface{}) {
if globalOptions.Quiet {
return
}
Printf(format, args...)
}
// PrintProgress wraps fmt.Printf to handle the difference in writing progress
// information to terminals and non-terminal stdout
func PrintProgress(format string, args ...interface{}) {
var (
message string
carriageControl string
)
message = fmt.Sprintf(format, args...)
if !(strings.HasSuffix(message, "\r") || strings.HasSuffix(message, "\n")) {
if stdoutIsTerminal() {
carriageControl = "\r"
} else {
carriageControl = "\n"
}
message = fmt.Sprintf("%s%s", message, carriageControl)
}
if stdoutIsTerminal() {
message = fmt.Sprintf("%s%s", ClearLine(), message)
}
fmt.Print(message)
}
// Warnf writes the message to the configured stderr stream.
func Warnf(format string, args ...interface{}) {
_, err := fmt.Fprintf(globalOptions.stderr, format, args...)
if err != nil {
fmt.Fprintf(os.Stderr, "unable to write to stderr: %v\n", err)
Exit(100)
}
}
// Exitf uses Warnf to write the message and then terminates the process with
// the given exit code.
func Exitf(exitcode int, format string, args ...interface{}) {
if format[len(format)-1] != '\n' {
format += "\n"
}
Warnf(format, args...)
Exit(exitcode)
}
// resolvePassword determines the password to be used for opening the repository.
func resolvePassword(opts GlobalOptions, env string) (string, error) {
if opts.PasswordFile != "" {
s, err := ioutil.ReadFile(opts.PasswordFile)
if os.IsNotExist(err) {
return "", errors.Fatalf("%s does not exist", opts.PasswordFile)
}
return strings.TrimSpace(string(s)), errors.Wrap(err, "Readfile")
}
if pwd := os.Getenv(env); pwd != "" {
return pwd, nil
}
return "", nil
}
// readPassword reads the password from the given reader directly.
func readPassword(in io.Reader) (password string, err error) {
buf := make([]byte, 1000)
n, err := io.ReadFull(in, buf)
buf = buf[:n]
if err != nil && errors.Cause(err) != io.ErrUnexpectedEOF {
return "", errors.Wrap(err, "ReadFull")
}
return strings.TrimRight(string(buf), "\r\n"), nil
}
// readPasswordTerminal reads the password from the given reader which must be a
// tty. Prompt is printed on the writer out before attempting to read the
// password.
func readPasswordTerminal(in *os.File, out io.Writer, prompt string) (password string, err error) {
fmt.Fprint(out, prompt)
buf, err := terminal.ReadPassword(int(in.Fd()))
fmt.Fprintln(out)
if err != nil {
return "", errors.Wrap(err, "ReadPassword")
}
password = string(buf)
return password, nil
}
// ReadPassword reads the password from a password file, the environment
// variable RESTIC_PASSWORD or prompts the user.
func ReadPassword(opts GlobalOptions, prompt string) (string, error) {
if opts.password != "" {
return opts.password, nil
}
var (
password string
err error
)
if stdinIsTerminal() {
password, err = readPasswordTerminal(os.Stdin, os.Stderr, prompt)
} else {
password, err = readPassword(os.Stdin)
}
if err != nil {
return "", errors.Wrap(err, "unable to read password")
}
if len(password) == 0 {
return "", errors.Fatal("an empty password is not a password")
}
return password, nil
}
// ReadPasswordTwice calls ReadPassword two times and returns an error when the
// passwords don't match.
func ReadPasswordTwice(gopts GlobalOptions, prompt1, prompt2 string) (string, error) {
pw1, err := ReadPassword(gopts, prompt1)
if err != nil {
return "", err
}
pw2, err := ReadPassword(gopts, prompt2)
if err != nil {
return "", err
}
if pw1 != pw2 {
return "", errors.Fatal("passwords do not match")
}
return pw1, nil
}
const maxKeys = 20
// OpenRepository reads the password and opens the repository.
func OpenRepository(opts GlobalOptions) (*repository.Repository, error) {
if opts.Repo == "" {
return nil, errors.Fatal("Please specify repository location (-r)")
}
be, err := open(opts.Repo, opts.extended)
if err != nil {
return nil, err
}
if opts.LimitUploadKb > 0 || opts.LimitDownloadKb > 0 {
debug.Log("rate limiting backend to %d KiB/s upload and %d KiB/s download", opts.LimitUploadKb, opts.LimitDownloadKb)
be = limiter.LimitBackend(be, limiter.NewStaticLimiter(opts.LimitUploadKb, opts.LimitDownloadKb))
}
be = backend.NewRetryBackend(be, 10, func(msg string, err error, d time.Duration) {
Warnf("%v returned error, retrying after %v: %v\n", msg, d, err)
})
s := repository.New(be)
opts.password, err = ReadPassword(opts, "enter password for repository: ")
if err != nil {
return nil, err
}
err = s.SearchKey(context.TODO(), opts.password, maxKeys)
if err != nil {
return nil, err
}
if stdoutIsTerminal() {
Verbosef("password is correct\n")
}
if opts.NoCache {
return s, nil
}
cache, err := cache.New(s.Config().ID, opts.CacheDir)
if err != nil {
Warnf("unable to open cache: %v\n", err)
} else {
s.UseCache(cache)
}
return s, nil
}
func parseConfig(loc location.Location, opts options.Options) (interface{}, error) {
// only apply options for a particular backend here
opts = opts.Extract(loc.Scheme)
switch loc.Scheme {
case "local":
cfg := loc.Config.(local.Config)
if err := opts.Apply(loc.Scheme, &cfg); err != nil {
return nil, err
}
debug.Log("opening local repository at %#v", cfg)
return cfg, nil
case "sftp":
cfg := loc.Config.(sftp.Config)
if err := opts.Apply(loc.Scheme, &cfg); err != nil {
return nil, err
}
debug.Log("opening sftp repository at %#v", cfg)
return cfg, nil
case "s3":
cfg := loc.Config.(s3.Config)
if cfg.KeyID == "" {
cfg.KeyID = os.Getenv("AWS_ACCESS_KEY_ID")
}
if cfg.Secret == "" {
cfg.Secret = os.Getenv("AWS_SECRET_ACCESS_KEY")
}
if err := opts.Apply(loc.Scheme, &cfg); err != nil {
return nil, err
}
debug.Log("opening s3 repository at %#v", cfg)
return cfg, nil
case "gs":
cfg := loc.Config.(gs.Config)
if cfg.ProjectID == "" {
cfg.ProjectID = os.Getenv("GOOGLE_PROJECT_ID")
}
if cfg.JSONKeyPath == "" {
if path := os.Getenv("GOOGLE_APPLICATION_CREDENTIALS"); path != "" {
// Check read access
if _, err := ioutil.ReadFile(path); err != nil {
return nil, errors.Fatalf("Failed to read google credential from file %v: %v", path, err)
}
cfg.JSONKeyPath = path
} else {
return nil, errors.Fatal("No credential file path is set")
}
}
if err := opts.Apply(loc.Scheme, &cfg); err != nil {
return nil, err
}
debug.Log("opening gs repository at %#v", cfg)
return cfg, nil
case "azure":
cfg := loc.Config.(azure.Config)
if cfg.AccountName == "" {
cfg.AccountName = os.Getenv("AZURE_ACCOUNT_NAME")
}
if cfg.AccountKey == "" {
cfg.AccountKey = os.Getenv("AZURE_ACCOUNT_KEY")
}
if err := opts.Apply(loc.Scheme, &cfg); err != nil {
return nil, err
}
debug.Log("opening gs repository at %#v", cfg)
return cfg, nil
case "swift":
cfg := loc.Config.(swift.Config)
if err := swift.ApplyEnvironment("", &cfg); err != nil {
return nil, err
}
if err := opts.Apply(loc.Scheme, &cfg); err != nil {
return nil, err
}
debug.Log("opening swift repository at %#v", cfg)
return cfg, nil
case "b2":
cfg := loc.Config.(b2.Config)
if cfg.AccountID == "" {
cfg.AccountID = os.Getenv("B2_ACCOUNT_ID")
}
if cfg.Key == "" {
cfg.Key = os.Getenv("B2_ACCOUNT_KEY")
}
if err := opts.Apply(loc.Scheme, &cfg); err != nil {
return nil, err
}
debug.Log("opening b2 repository at %#v", cfg)
return cfg, nil
case "rest":
cfg := loc.Config.(rest.Config)
if err := opts.Apply(loc.Scheme, &cfg); err != nil {
return nil, err
}
debug.Log("opening rest repository at %#v", cfg)
return cfg, nil
}
return nil, errors.Fatalf("invalid backend: %q", loc.Scheme)
}
// Open the backend specified by a location config.
func open(s string, opts options.Options) (restic.Backend, error) {
debug.Log("parsing location %v", s)
loc, err := location.Parse(s)
if err != nil {
return nil, errors.Fatalf("parsing repository location failed: %v", err)
}
var be restic.Backend
cfg, err := parseConfig(loc, opts)
if err != nil {
return nil, err
}
rt, err := backend.Transport(globalOptions.CACerts)
if err != nil {
return nil, err
}
switch loc.Scheme {
case "local":
be, err = local.Open(cfg.(local.Config))
case "sftp":
be, err = sftp.Open(cfg.(sftp.Config), SuspendSignalHandler, InstallSignalHandler)
case "s3":
be, err = s3.Open(cfg.(s3.Config), rt)
case "gs":
be, err = gs.Open(cfg.(gs.Config))
case "azure":
be, err = azure.Open(cfg.(azure.Config), rt)
case "swift":
be, err = swift.Open(cfg.(swift.Config), rt)
case "b2":
be, err = b2.Open(cfg.(b2.Config), rt)
case "rest":
be, err = rest.Open(cfg.(rest.Config), rt)
default:
return nil, errors.Fatalf("invalid backend: %q", loc.Scheme)
}
if err != nil {
return nil, errors.Fatalf("unable to open repo at %v: %v", s, err)
}
// check if config is there
fi, err := be.Stat(context.TODO(), restic.Handle{Type: restic.ConfigFile})
if err != nil {
return nil, errors.Fatalf("unable to open config file: %v\nIs there a repository at the following location?\n%v", err, s)
}
if fi.Size == 0 {
return nil, errors.New("config file has zero size, invalid repository?")
}
return be, nil
}
// Create the backend specified by URI.
func create(s string, opts options.Options) (restic.Backend, error) {
debug.Log("parsing location %v", s)
loc, err := location.Parse(s)
if err != nil {
return nil, err
}
cfg, err := parseConfig(loc, opts)
if err != nil {
return nil, err
}
rt, err := backend.Transport(globalOptions.CACerts)
if err != nil {
return nil, err
}
switch loc.Scheme {
case "local":
return local.Create(cfg.(local.Config))
case "sftp":
return sftp.Create(cfg.(sftp.Config), SuspendSignalHandler, InstallSignalHandler)
case "s3":
return s3.Create(cfg.(s3.Config), rt)
case "gs":
return gs.Create(cfg.(gs.Config))
case "azure":
return azure.Create(cfg.(azure.Config), rt)
case "swift":
return swift.Open(cfg.(swift.Config), rt)
case "b2":
return b2.Create(cfg.(b2.Config), rt)
case "rest":
return rest.Create(cfg.(rest.Config), rt)
}
debug.Log("invalid repository scheme: %v", s)
return nil, errors.Fatalf("invalid scheme %q", loc.Scheme)
}

View File

@@ -7,8 +7,9 @@ import (
"net/http"
_ "net/http/pprof"
"os"
"restic/errors"
"restic/repository"
"github.com/restic/restic/internal/errors"
"github.com/restic/restic/internal/repository"
"github.com/pkg/profile"
)
@@ -18,10 +19,6 @@ var (
memProfilePath string
cpuProfilePath string
insecure bool
prof interface {
Stop()
}
)
func init() {
@@ -53,10 +50,21 @@ func runDebug() error {
return errors.Fatal("only one profile (memory or CPU) may be activated at the same time")
}
var prof interface {
Stop()
}
if memProfilePath != "" {
prof = profile.Start(profile.Quiet, profile.MemProfile, profile.ProfilePath(memProfilePath))
prof = profile.Start(profile.Quiet, profile.NoShutdownHook, profile.MemProfile, profile.ProfilePath(memProfilePath))
} else if cpuProfilePath != "" {
prof = profile.Start(profile.Quiet, profile.CPUProfile, profile.ProfilePath(cpuProfilePath))
prof = profile.Start(profile.Quiet, profile.NoShutdownHook, profile.CPUProfile, profile.ProfilePath(cpuProfilePath))
}
if prof != nil {
AddCleanupHandler(func() error {
prof.Stop()
return nil
})
}
if insecure {
@@ -65,9 +73,3 @@ func runDebug() error {
return nil
}
func shutdownDebug() {
if prof != nil {
prof.Stop()
}
}

View File

@@ -0,0 +1,6 @@
// +build !debug
package main
// runDebug is a noop without the debug tag.
func runDebug() error { return nil }

View File

@@ -0,0 +1,216 @@
// +build !openbsd
// +build !windows
package main
import (
"context"
"fmt"
"os"
"path/filepath"
"testing"
"time"
"github.com/restic/restic/internal/repository"
"github.com/restic/restic/internal/restic"
rtest "github.com/restic/restic/internal/test"
)
const (
mountWait = 20
mountSleep = 100 * time.Millisecond
mountTestSubdir = "snapshots"
)
func snapshotsDirExists(t testing.TB, dir string) bool {
f, err := os.Open(filepath.Join(dir, mountTestSubdir))
if err != nil && os.IsNotExist(err) {
return false
}
if err != nil {
t.Error(err)
}
if err := f.Close(); err != nil {
t.Error(err)
}
return true
}
// waitForMount blocks (max mountWait * mountSleep) until the subdir
// "snapshots" appears in the dir.
func waitForMount(t testing.TB, dir string) {
for i := 0; i < mountWait; i++ {
if snapshotsDirExists(t, dir) {
t.Log("mounted directory is ready")
return
}
time.Sleep(mountSleep)
}
t.Errorf("subdir %q of dir %s never appeared", mountTestSubdir, dir)
}
func testRunMount(t testing.TB, gopts GlobalOptions, dir string) {
opts := MountOptions{}
rtest.OK(t, runMount(opts, gopts, []string{dir}))
}
func testRunUmount(t testing.TB, gopts GlobalOptions, dir string) {
var err error
for i := 0; i < mountWait; i++ {
if err = umount(dir); err == nil {
t.Logf("directory %v umounted", dir)
return
}
time.Sleep(mountSleep)
}
t.Errorf("unable to umount dir %v, last error was: %v", dir, err)
}
func listSnapshots(t testing.TB, dir string) []string {
snapshotsDir, err := os.Open(filepath.Join(dir, "snapshots"))
rtest.OK(t, err)
names, err := snapshotsDir.Readdirnames(-1)
rtest.OK(t, err)
rtest.OK(t, snapshotsDir.Close())
return names
}
func checkSnapshots(t testing.TB, global GlobalOptions, repo *repository.Repository, mountpoint, repodir string, snapshotIDs restic.IDs, expectedSnapshotsInFuseDir int) {
t.Logf("checking for %d snapshots: %v", len(snapshotIDs), snapshotIDs)
go testRunMount(t, global, mountpoint)
waitForMount(t, mountpoint)
defer testRunUmount(t, global, mountpoint)
if !snapshotsDirExists(t, mountpoint) {
t.Fatal(`virtual directory "snapshots" doesn't exist`)
}
ids := listSnapshots(t, repodir)
t.Logf("found %v snapshots in repo: %v", len(ids), ids)
namesInSnapshots := listSnapshots(t, mountpoint)
t.Logf("found %v snapshots in fuse mount: %v", len(namesInSnapshots), namesInSnapshots)
rtest.Assert(t,
expectedSnapshotsInFuseDir == len(namesInSnapshots),
"Invalid number of snapshots: expected %d, got %d", expectedSnapshotsInFuseDir, len(namesInSnapshots))
namesMap := make(map[string]bool)
for _, name := range namesInSnapshots {
namesMap[name] = false
}
// Is "latest" present?
if len(namesMap) != 0 {
_, ok := namesMap["latest"]
if !ok {
t.Errorf("Symlink latest isn't present in fuse dir")
} else {
namesMap["latest"] = true
}
}
for _, id := range snapshotIDs {
snapshot, err := restic.LoadSnapshot(context.TODO(), repo, id)
rtest.OK(t, err)
ts := snapshot.Time.Format(time.RFC3339)
present, ok := namesMap[ts]
if !ok {
t.Errorf("Snapshot %v (%q) isn't present in fuse dir", id.Str(), ts)
}
for i := 1; present; i++ {
ts = fmt.Sprintf("%s-%d", snapshot.Time.Format(time.RFC3339), i)
present, ok = namesMap[ts]
if !ok {
t.Errorf("Snapshot %v (%q) isn't present in fuse dir", id.Str(), ts)
}
if !present {
break
}
}
namesMap[ts] = true
}
for name, present := range namesMap {
rtest.Assert(t, present, "Directory %s is present in fuse dir but is not a snapshot", name)
}
}
func TestMount(t *testing.T) {
if !rtest.RunFuseTest {
t.Skip("Skipping fuse tests")
}
env, cleanup := withTestEnvironment(t)
defer cleanup()
testRunInit(t, env.gopts)
repo, err := OpenRepository(env.gopts)
rtest.OK(t, err)
// We remove the mountpoint now to check that cmdMount creates it
rtest.RemoveAll(t, env.mountpoint)
checkSnapshots(t, env.gopts, repo, env.mountpoint, env.repo, []restic.ID{}, 0)
rtest.SetupTarTestFixture(t, env.testdata, filepath.Join("testdata", "backup-data.tar.gz"))
// first backup
testRunBackup(t, []string{env.testdata}, BackupOptions{}, env.gopts)
snapshotIDs := testRunList(t, "snapshots", env.gopts)
rtest.Assert(t, len(snapshotIDs) == 1,
"expected one snapshot, got %v", snapshotIDs)
checkSnapshots(t, env.gopts, repo, env.mountpoint, env.repo, snapshotIDs, 2)
// second backup, implicit incremental
testRunBackup(t, []string{env.testdata}, BackupOptions{}, env.gopts)
snapshotIDs = testRunList(t, "snapshots", env.gopts)
rtest.Assert(t, len(snapshotIDs) == 2,
"expected two snapshots, got %v", snapshotIDs)
checkSnapshots(t, env.gopts, repo, env.mountpoint, env.repo, snapshotIDs, 3)
// third backup, explicit incremental
bopts := BackupOptions{Parent: snapshotIDs[0].String()}
testRunBackup(t, []string{env.testdata}, bopts, env.gopts)
snapshotIDs = testRunList(t, "snapshots", env.gopts)
rtest.Assert(t, len(snapshotIDs) == 3,
"expected three snapshots, got %v", snapshotIDs)
checkSnapshots(t, env.gopts, repo, env.mountpoint, env.repo, snapshotIDs, 4)
}
func TestMountSameTimestamps(t *testing.T) {
if !rtest.RunFuseTest {
t.Skip("Skipping fuse tests")
}
env, cleanup := withTestEnvironment(t)
defer cleanup()
rtest.SetupTarTestFixture(t, env.base, filepath.Join("testdata", "repo-same-timestamps.tar.gz"))
repo, err := OpenRepository(env.gopts)
rtest.OK(t, err)
ids := []restic.ID{
restic.TestParseID("280303689e5027328889a06d718b729e96a1ce6ae9ef8290bff550459ae611ee"),
restic.TestParseID("75ad6cdc0868e082f2596d5ab8705e9f7d87316f5bf5690385eeff8dbe49d9f5"),
restic.TestParseID("5fd0d8b2ef0fa5d23e58f1e460188abb0f525c0f0c4af8365a1280c807a80a1b"),
}
checkSnapshots(t, env.gopts, repo, env.mountpoint, env.repo, ids, 4)
}

View File

@@ -0,0 +1,232 @@
package main
import (
"context"
"fmt"
"io/ioutil"
"os"
"path/filepath"
"runtime"
"testing"
"github.com/restic/restic/internal/options"
"github.com/restic/restic/internal/repository"
rtest "github.com/restic/restic/internal/test"
)
type dirEntry struct {
path string
fi os.FileInfo
link uint64
}
func walkDir(dir string) <-chan *dirEntry {
ch := make(chan *dirEntry, 100)
go func() {
err := filepath.Walk(dir, func(path string, info os.FileInfo, err error) error {
if err != nil {
fmt.Fprintf(os.Stderr, "error: %v\n", err)
return nil
}
name, err := filepath.Rel(dir, path)
if err != nil {
fmt.Fprintf(os.Stderr, "error: %v\n", err)
return nil
}
ch <- &dirEntry{
path: name,
fi: info,
link: nlink(info),
}
return nil
})
if err != nil {
fmt.Fprintf(os.Stderr, "Walk() error: %v\n", err)
}
close(ch)
}()
// first element is root
_ = <-ch
return ch
}
func isSymlink(fi os.FileInfo) bool {
mode := fi.Mode() & (os.ModeType | os.ModeCharDevice)
return mode == os.ModeSymlink
}
func sameModTime(fi1, fi2 os.FileInfo) bool {
switch runtime.GOOS {
case "darwin", "freebsd", "openbsd":
if isSymlink(fi1) && isSymlink(fi2) {
return true
}
}
same := fi1.ModTime().Equal(fi2.ModTime())
if !same && (runtime.GOOS == "darwin" || runtime.GOOS == "openbsd") {
// Allow up to 1μs difference, because macOS <10.13 cannot restore
// with nanosecond precision and the current version of Go (1.9.2)
// does not yet support the new syscall. (#1087)
mt1 := fi1.ModTime()
mt2 := fi2.ModTime()
usecDiff := (mt1.Nanosecond()-mt2.Nanosecond())/1000 + (mt1.Second()-mt2.Second())*1000000
same = usecDiff <= 1 && usecDiff >= -1
}
return same
}
// directoriesEqualContents checks if both directories contain exactly the same
// contents.
func directoriesEqualContents(dir1, dir2 string) bool {
ch1 := walkDir(dir1)
ch2 := walkDir(dir2)
changes := false
var a, b *dirEntry
for {
var ok bool
if ch1 != nil && a == nil {
a, ok = <-ch1
if !ok {
ch1 = nil
}
}
if ch2 != nil && b == nil {
b, ok = <-ch2
if !ok {
ch2 = nil
}
}
if ch1 == nil && ch2 == nil {
break
}
if ch1 == nil {
fmt.Printf("+%v\n", b.path)
changes = true
} else if ch2 == nil {
fmt.Printf("-%v\n", a.path)
changes = true
} else if !a.equals(b) {
if a.path < b.path {
fmt.Printf("-%v\n", a.path)
changes = true
a = nil
continue
} else if a.path > b.path {
fmt.Printf("+%v\n", b.path)
changes = true
b = nil
continue
} else {
fmt.Printf("%%%v\n", a.path)
changes = true
}
}
a, b = nil, nil
}
if changes {
return false
}
return true
}
type dirStat struct {
files, dirs, other uint
size uint64
}
func isFile(fi os.FileInfo) bool {
return fi.Mode()&(os.ModeType|os.ModeCharDevice) == 0
}
// dirStats walks dir and collects stats.
func dirStats(dir string) (stat dirStat) {
for entry := range walkDir(dir) {
if isFile(entry.fi) {
stat.files++
stat.size += uint64(entry.fi.Size())
continue
}
if entry.fi.IsDir() {
stat.dirs++
continue
}
stat.other++
}
return stat
}
type testEnvironment struct {
base, cache, repo, mountpoint, testdata string
gopts GlobalOptions
}
// withTestEnvironment creates a test environment and returns a cleanup
// function which removes it.
func withTestEnvironment(t testing.TB) (env *testEnvironment, cleanup func()) {
if !rtest.RunIntegrationTest {
t.Skip("integration tests disabled")
}
repository.TestUseLowSecurityKDFParameters(t)
tempdir, err := ioutil.TempDir(rtest.TestTempDir, "restic-test-")
rtest.OK(t, err)
env = &testEnvironment{
base: tempdir,
cache: filepath.Join(tempdir, "cache"),
repo: filepath.Join(tempdir, "repo"),
testdata: filepath.Join(tempdir, "testdata"),
mountpoint: filepath.Join(tempdir, "mount"),
}
rtest.OK(t, os.MkdirAll(env.mountpoint, 0700))
rtest.OK(t, os.MkdirAll(env.testdata, 0700))
rtest.OK(t, os.MkdirAll(env.cache, 0700))
rtest.OK(t, os.MkdirAll(env.repo, 0700))
env.gopts = GlobalOptions{
Repo: env.repo,
Quiet: true,
CacheDir: env.cache,
ctx: context.Background(),
password: rtest.TestPassword,
stdout: os.Stdout,
stderr: os.Stderr,
extended: make(options.Options),
}
// always overwrite global options
globalOptions = env.gopts
cleanup = func() {
if !rtest.TestCleanupTempDirs {
t.Logf("leaving temporary directory %v used for test", tempdir)
return
}
rtest.RemoveAll(t, tempdir)
}
return env, cleanup
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,41 @@
package main
import (
"path/filepath"
"testing"
rtest "github.com/restic/restic/internal/test"
)
func TestRestoreLocalLayout(t *testing.T) {
env, cleanup := withTestEnvironment(t)
defer cleanup()
var tests = []struct {
filename string
layout string
}{
{"repo-layout-default.tar.gz", ""},
{"repo-layout-s3legacy.tar.gz", ""},
{"repo-layout-default.tar.gz", "default"},
{"repo-layout-s3legacy.tar.gz", "s3legacy"},
}
for _, test := range tests {
datafile := filepath.Join("..", "..", "internal", "backend", "testdata", test.filename)
rtest.SetupTarTestFixture(t, env.base, datafile)
env.gopts.extended["local.layout"] = test.layout
// check the repo
testRunCheck(t, env.gopts)
// restore latest snapshot
target := filepath.Join(env.base, "restore")
testRunRestoreLatest(t, env.gopts, target, nil, "")
rtest.RemoveAll(t, filepath.Join(env.base, "repo"))
rtest.RemoveAll(t, target)
}
}

128
cmd/restic/lock.go Normal file
View File

@@ -0,0 +1,128 @@
package main
import (
"context"
"fmt"
"os"
"sync"
"time"
"github.com/restic/restic/internal/debug"
"github.com/restic/restic/internal/errors"
"github.com/restic/restic/internal/repository"
"github.com/restic/restic/internal/restic"
)
var globalLocks struct {
locks []*restic.Lock
cancelRefresh chan struct{}
refreshWG sync.WaitGroup
sync.Mutex
}
func lockRepo(repo *repository.Repository) (*restic.Lock, error) {
return lockRepository(repo, false)
}
func lockRepoExclusive(repo *repository.Repository) (*restic.Lock, error) {
return lockRepository(repo, true)
}
func lockRepository(repo *repository.Repository, exclusive bool) (*restic.Lock, error) {
lockFn := restic.NewLock
if exclusive {
lockFn = restic.NewExclusiveLock
}
lock, err := lockFn(context.TODO(), repo)
if err != nil {
return nil, errors.Fatalf("unable to create lock in backend: %v", err)
}
debug.Log("create lock %p (exclusive %v)", lock, exclusive)
globalLocks.Lock()
if globalLocks.cancelRefresh == nil {
debug.Log("start goroutine for lock refresh")
globalLocks.cancelRefresh = make(chan struct{})
globalLocks.refreshWG = sync.WaitGroup{}
globalLocks.refreshWG.Add(1)
go refreshLocks(&globalLocks.refreshWG, globalLocks.cancelRefresh)
}
globalLocks.locks = append(globalLocks.locks, lock)
globalLocks.Unlock()
return lock, err
}
var refreshInterval = 5 * time.Minute
func refreshLocks(wg *sync.WaitGroup, done <-chan struct{}) {
debug.Log("start")
defer func() {
wg.Done()
globalLocks.Lock()
globalLocks.cancelRefresh = nil
globalLocks.Unlock()
}()
ticker := time.NewTicker(refreshInterval)
for {
select {
case <-done:
debug.Log("terminate")
return
case <-ticker.C:
debug.Log("refreshing locks")
globalLocks.Lock()
for _, lock := range globalLocks.locks {
err := lock.Refresh(context.TODO())
if err != nil {
fmt.Fprintf(os.Stderr, "unable to refresh lock: %v\n", err)
}
}
globalLocks.Unlock()
}
}
}
func unlockRepo(lock *restic.Lock) error {
globalLocks.Lock()
defer globalLocks.Unlock()
debug.Log("unlocking repository with lock %p", lock)
if err := lock.Unlock(); err != nil {
debug.Log("error while unlocking: %v", err)
return err
}
for i := 0; i < len(globalLocks.locks); i++ {
if lock == globalLocks.locks[i] {
globalLocks.locks = append(globalLocks.locks[:i], globalLocks.locks[i+1:]...)
return nil
}
}
return nil
}
func unlockAll() error {
globalLocks.Lock()
defer globalLocks.Unlock()
debug.Log("unlocking %d locks", len(globalLocks.locks))
for _, lock := range globalLocks.locks {
if err := lock.Unlock(); err != nil {
debug.Log("error while unlocking: %v", err)
return err
}
debug.Log("successfully removed lock")
}
return nil
}
func init() {
AddCleanupHandler(unlockAll)
}

94
cmd/restic/main.go Normal file
View File

@@ -0,0 +1,94 @@
package main
import (
"bufio"
"bytes"
"fmt"
"log"
"os"
"runtime"
"github.com/restic/restic/internal/debug"
"github.com/restic/restic/internal/options"
"github.com/restic/restic/internal/restic"
"github.com/spf13/cobra"
"github.com/restic/restic/internal/errors"
)
// cmdRoot is the base command when no other command has been specified.
var cmdRoot = &cobra.Command{
Use: "restic",
Short: "Backup and restore files",
Long: `
restic is a backup program which allows saving multiple revisions of files and
directories in an encrypted repository stored on different backends.
`,
SilenceErrors: true,
SilenceUsage: true,
DisableAutoGenTag: true,
PersistentPreRunE: func(*cobra.Command, []string) error {
// parse extended options
opts, err := options.Parse(globalOptions.Options)
if err != nil {
return err
}
globalOptions.extended = opts
pwd, err := resolvePassword(globalOptions, "RESTIC_PASSWORD")
if err != nil {
fmt.Fprintf(os.Stderr, "Resolving password failed: %v\n", err)
Exit(1)
}
globalOptions.password = pwd
// run the debug functions for all subcommands (if build tag "debug" is
// enabled)
if err := runDebug(); err != nil {
return err
}
return nil
},
}
var logBuffer = bytes.NewBuffer(nil)
func init() {
// install custom global logger into a buffer, if an error occurs
// we can show the logs
log.SetOutput(logBuffer)
}
func main() {
debug.Log("main %#v", os.Args)
debug.Log("restic %s, compiled with %v on %v/%v",
version, runtime.Version(), runtime.GOOS, runtime.GOARCH)
err := cmdRoot.Execute()
switch {
case restic.IsAlreadyLocked(errors.Cause(err)):
fmt.Fprintf(os.Stderr, "%v\nthe `unlock` command can be used to remove stale locks\n", err)
case errors.IsFatal(errors.Cause(err)):
fmt.Fprintf(os.Stderr, "%v\n", err)
case err != nil:
fmt.Fprintf(os.Stderr, "%+v\n", err)
if logBuffer.Len() > 0 {
fmt.Fprintf(os.Stderr, "also, the following messages were logged by a library:\n")
sc := bufio.NewScanner(logBuffer)
for sc.Scan() {
fmt.Fprintln(os.Stderr, sc.Text())
}
}
}
var exitCode int
if err != nil {
exitCode = 1
}
Exit(exitCode)
}

63
cmd/restic/table.go Normal file
View File

@@ -0,0 +1,63 @@
package main
import (
"fmt"
"io"
"strings"
)
// Table contains data for a table to be printed.
type Table struct {
Header string
Rows [][]interface{}
Footer string
RowFormat string
}
// NewTable initializes a new Table.
func NewTable() Table {
return Table{
Rows: [][]interface{}{},
}
}
func (t Table) printSeparationLine(w io.Writer) error {
_, err := fmt.Fprintln(w, strings.Repeat("-", 70))
return err
}
// Write prints the table to w.
func (t Table) Write(w io.Writer) error {
_, err := fmt.Fprintln(w, t.Header)
if err != nil {
return err
}
err = t.printSeparationLine(w)
if err != nil {
return err
}
for _, row := range t.Rows {
_, err = fmt.Fprintf(w, t.RowFormat+"\n", row...)
if err != nil {
return err
}
}
err = t.printSeparationLine(w)
if err != nil {
return err
}
_, err = fmt.Fprintln(w, t.Footer)
if err != nil {
return err
}
return nil
}
// TimeFormat is the format used for all timestamps printed by restic.
const TimeFormat = "2006-01-02 15:04:05"

BIN
cmd/restic/testdata/small-repo.tar.gz vendored Normal file

Binary file not shown.

16
doc/010_introduction.rst Normal file
View File

@@ -0,0 +1,16 @@
..
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
############
Introduction
############

149
doc/020_installation.rst Normal file
View File

@@ -0,0 +1,149 @@
..
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
############
Installation
############
Packages
********
Mac OS X
========
If you are using Mac OS X, you can install restic using the
`homebrew <http://brew.sh/>`__ package manager:
.. code-block:: console
$ brew install restic
Arch Linux
==========
On `Arch Linux <https://www.archlinux.org/>`__, there is a package called ``restic-git``
which can be installed from AUR, e.g. with ``pacaur``:
.. code-block:: console
$ pacaur -S restic-git
Nix & NixOS
===========
If you are using `Nix <https://nixos.org/nix/>`__ or `NixOS <https://nixos.org/>`__
there is a package available named ``restic``.
It can be installed uisng ``nix-env``:
.. code-block:: console
$ nix-env --install restic
Debian
======
On Debian, there's a package called ``restic`` which can be
installed from the official repos, e.g. with ``apt-get``:
.. code-block:: console
$ apt-get install restic
.. warning:: Please be aware that, at the time of writing, Debian *stable*
has ``restic`` version 0.3.3 which is very old. The *testing* and *unstable*
branches have recent versions of ``restic``.
Pre-compiled Binary
*******************
You can download the latest pre-compiled binary from the `restic release
page <https://github.com/restic/restic/releases/latest>`__.
Docker Container
****************
.. note::
| A docker container is available as a contribution (Thank you!).
| You can find it at https://github.com/Lobaro/restic-backup-docker
From Source
***********
restic is written in the Go programming language and you need at least
Go version 1.8. 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.
In order to build restic from source, execute the following steps:
.. code-block:: console
$ git clone https://github.com/restic/restic
[...]
$ cd restic
$ go run build.go
You can easily cross-compile restic for all supported platforms, just
supply the target OS and platform via the command-line options like this
(for Windows and FreeBSD respectively):
::
$ go run build.go --goos windows --goarch amd64
$ go run build.go --goos freebsd --goarch 386
The resulting binary is statically linked and does not require any
libraries.
At the moment, the only tested compiler for restic is the official Go
compiler. Building restic with gccgo may work, but is not supported.
Autocompletion
**************
Restic can write out a bash compatible autocompletion script:
.. code-block:: console
$ ./restic autocomplete --help
The "autocomplete" command generates a shell autocompletion script.
NOTE: The current version supports Bash only.
This should work for *nix systems with Bash installed.
By default, the file is written directly to ``/etc/bash_completion.d/``
for convenience, and the command may need superuser rights, e.g.
.. code-block:: console
$ sudo restic autocomplete
Usage:
restic autocomplete [flags]
Flags:
--completionfile string autocompletion file (default "/etc/bash_completion.d/restic.sh")
Global Flags:
--json set output mode to JSON for commands that support it
--no-lock do not lock the repo, this allows some operations on read-only repos
-o, --option key=value set extended option (key=value, can be specified multiple times)
-p, --password-file string read the repository password from a file
-q, --quiet do not output comprehensive progress report
-r, --repo string repository to backup to or restore from (default: $RESTIC_REPOSITORY)

View File

@@ -0,0 +1,402 @@
..
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
##########################
Preparing a new repository
##########################
The place where your backups will be saved at is called a "repository".
This chapter explains how to create ("init") such a repository.
Local
*****
In order to create a repository at ``/tmp/backup``, run the following
command and enter the same password twice:
.. code-block:: console
$ restic init --repo /tmp/backup
enter password for new backend:
enter password again:
created restic backend 085b3c76b9 at /tmp/backup
Please note that knowledge of your password is required to access the repository.
Losing your password means that your data is irrecoverably lost.
.. warning::
Remembering your password is important! If you lose it, you won't be
able to access data stored in the repository.
For automated backups, restic accepts the repository location in the
environment variable ``RESTIC_REPOSITORY``. The password can be read
from a file (via the option ``--password-file`` or the environment variable
``RESTIC_PASSWORD_FILE``) or the environment variable ``RESTIC_PASSWORD``.
SFTP
****
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 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:
.. code-block:: console
$ restic -r sftp:user@host:/tmp/backup init
enter password for new backend:
enter password again:
created restic backend f1c6108821 at sftp:user@host:/tmp/backup
Please note that knowledge of your password is required to access the repository.
Losing your password means that your data is irrecoverably lost.
You can also specify a relative (read: no slash (``/``) character at the
beginning) directory, in this case the dir is relative to the remote
user's home directory.
.. note:: Please be aware that sftp servers do not expand the tilde character
(``~``) normally used as an alias for a user's home directory. If you
want to specify a path relative to the user's home directory, pass a
relative path to the sftp backend.
The backend config string does not allow specifying a port. If you need
to contact an sftp server on a different port, you can create an entry
in the ``ssh`` file, usually located in your user's home directory at
``~/.ssh/config`` or in ``/etc/ssh/ssh_config``:
::
Host foo
User bar
Port 2222
Then use the specified host name ``foo`` normally (you don't need to
specify the user name in this case):
::
$ restic -r sftp:foo:/tmp/backup init
You can also add an entry with a special host name which does not exist,
just for use with restic, and use the ``Hostname`` option to set the
real host name:
::
Host restic-backup-host
Hostname foo
User bar
Port 2222
Then use it in the backend specification:
::
$ restic -r sftp:restic-backup-host:/tmp/backup init
Last, if you'd like to use an entirely different program to create the
SFTP connection, you can specify the command to be run with the option
``-o sftp.command="foobar"``.
REST Server
***********
In order to backup data to the remote server via HTTP or HTTPS protocol,
you must first set up a remote `REST
server <https://github.com/restic/rest-server>`__ instance. Once the
server is configured, accessing it is achieved by changing the URL
scheme like this:
.. code-block:: console
$ restic -r rest:http://host:8000/
Depending on your REST server setup, you can use HTTPS protocol,
password protection, or multiple repositories. Or any combination of
those features, as you see fit. TCP/IP port is also configurable. Here
are some more examples:
.. code-block:: console
$ 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
exits with an error. If you have your own self-signed certificate, or a custom
CA certificate should be used for verification, you can pass restic the
certificate filename via the `--cacert` option.
REST server uses exactly the same directory structure as local backend,
so you should be able to access it both locally and via HTTP, even
simultaneously.
Amazon S3
*********
Restic can backup data to any Amazon S3 bucket. However, in this case,
changing the URL scheme is not enough since Amazon uses special security
credentials to sign HTTP requests. By consequence, you must first setup
the following environment variables with the credentials you obtained
while creating the bucket.
.. code-block:: console
$ export AWS_ACCESS_KEY_ID=<MY_ACCESS_KEY>
$ export AWS_SECRET_ACCESS_KEY=<MY_SECRET_ACCESS_KEY>
You can then easily initialize a repository that uses your Amazon S3 as
a backend, if the bucket does not exist yet it will be created in the
default location:
.. code-block:: console
$ restic -r s3:s3.amazonaws.com/bucket_name init
enter password for new backend:
enter password again:
created restic backend eefee03bbd at s3:s3.amazonaws.com/bucket_name
Please note that knowledge of your password is required to access the repository.
Losing your password means that your data is irrecoverably lost.
It is not possible at the moment to have restic create a new bucket in a
different location, so you need to create it using a different program.
Afterwards, the S3 server (``s3.amazonaws.com``) will redirect restic to
the correct endpoint.
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
after the bucket name like this:
.. code-block:: console
$ restic -r s3:s3.amazonaws.com/bucket_name/restic [...]
For an S3-compatible server that is not Amazon (like Minio, see below),
or is only available via HTTP, you can specify the URL to the server
like this: ``s3:http://server:port/bucket_name``.
Minio Server
************
`Minio <https://www.minio.io>`__ is an Open Source Object Storage,
written in Go and compatible with AWS S3 API.
- Download and Install `Minio
Server <https://minio.io/downloads/#minio-server>`__.
- You can also refer to https://docs.minio.io for step by step guidance
on installation and getting started on Minio Client and Minio Server.
You must first setup the following environment variables with the
credentials of your running Minio Server.
.. code-block:: console
$ export AWS_ACCESS_KEY_ID=<YOUR-MINIO-ACCESS-KEY-ID>
$ export AWS_SECRET_ACCESS_KEY= <YOUR-MINIO-SECRET-ACCESS-KEY>
Now you can easily initialize restic to use Minio server as backend with
this command.
.. code-block:: console
$ ./restic -r s3:http://localhost:9000/restic init
enter password for new backend:
enter password again:
created restic backend 6ad29560f5 at s3:http://localhost:9000/restic1
Please note that knowledge of your password is required to access
the repository. Losing your password means that your data is irrecoverably lost.
OpenStack Swift
***************
Restic can backup data to an OpenStack Swift container. Because Swift supports
various authentication methods, credentials are passed through environment
variables. In order to help integration with existing OpenStack installations,
the naming convention of those variables follows official python swift client:
.. code-block:: console
# For keystone v1 authentication
$ export ST_AUTH=<MY_AUTH_URL>
$ export ST_USER=<MY_USER_NAME>
$ export ST_KEY=<MY_USER_PASSWORD>
# For keystone v2 authentication (some variables are optional)
$ export OS_AUTH_URL=<MY_AUTH_URL>
$ export OS_REGION_NAME=<MY_REGION_NAME>
$ export OS_USERNAME=<MY_USERNAME>
$ export OS_PASSWORD=<MY_PASSWORD>
$ export OS_TENANT_ID=<MY_TENANT_ID>
$ export OS_TENANT_NAME=<MY_TENANT_NAME>
# For keystone v3 authentication (some variables are optional)
$ export OS_AUTH_URL=<MY_AUTH_URL>
$ export OS_REGION_NAME=<MY_REGION_NAME>
$ export OS_USERNAME=<MY_USERNAME>
$ export OS_PASSWORD=<MY_PASSWORD>
$ export OS_USER_DOMAIN_NAME=<MY_DOMAIN_NAME>
$ export OS_PROJECT_NAME=<MY_PROJECT_NAME>
$ export OS_PROJECT_DOMAIN_NAME=<MY_PROJECT_DOMAIN_NAME>
# For authentication based on tokens
$ export OS_STORAGE_URL=<MY_STORAGE_URL>
$ export OS_AUTH_TOKEN=<MY_AUTH_TOKEN>
Restic should be compatible with `OpenStack RC file
<https://docs.openstack.org/user-guide/common/cli-set-environment-variables-using-openstack-rc.html>`__
in most cases.
Once environment variables are set up, a new repository can be created. The
name of swift container and optional path can be specified. If
the container does not exist, it will be created automatically:
.. code-block:: console
$ restic -r swift:container_name:/path init # path is optional
enter password for new backend:
enter password again:
created restic backend eefee03bbd at swift:container_name:/path
Please note that knowledge of your password is required to access the repository.
Losing your password means that your data is irrecoverably lost.
The policy of new container created by restic can be changed using environment variable:
.. code-block:: console
$ export SWIFT_DEFAULT_CONTAINER_POLICY=<MY_CONTAINER_POLICY>
Backblaze B2
************
Restic can backup data to any Backblaze B2 bucket. You need to first setup the
following environment variables with the credentials you obtained when signed
into your B2 account:
.. code-block:: console
$ export B2_ACCOUNT_ID=<MY_ACCOUNT_ID>
$ export B2_ACCOUNT_KEY=<MY_SECRET_ACCOUNT_KEY>
You can then easily initialize a repository stored at Backblaze B2. If the
bucket does not exist yet, it will be created:
.. code-block:: console
$ restic -r b2:bucketname:path/to/repo init
enter password for new backend:
enter password again:
created restic backend eefee03bbd at b2:bucketname:path/to/repo
Please note that knowledge of your password is required to access the repository.
Losing your password means that your data is irrecoverably lost.
The number of concurrent connections to the B2 service can be set with the `-o
b2.connections=10`. By default, at most five parallel connections are
established.
Microsoft Azure Blob Storage
****************************
You can also store backups on Microsoft Azure Blob Storage. Export the Azure
account name and key as follows:
.. code-block:: console
$ export AZURE_ACCOUNT_NAME=<ACCOUNT_NAME>
$ export AZURE_ACCOUNT_KEY=<SECRET_KEY>
Afterwards you can initialize a repository in a container called `foo` in the
root path like this:
.. code-block:: console
$ restic -r azure:foo:/ init
enter password for new backend:
enter password again:
created restic backend a934bac191 at azure:foo:/
[...]
The number of concurrent connections to the B2 service can be set with the
`-o azure.connections=10`. By default, at most five parallel connections are
established.
Google Cloud Storage
********************
Restic supports Google Cloud Storage as a backend.
Restic connects to Google Cloud Storage via a `service account`_.
For normal restic operation, the service account must have the
``storage.objects.{create,delete,get,list}`` permissions for the bucket. These
are included in the "Storage Object Admin" role.
``restic init`` can create the repository bucket. Doing so requires the
``storage.buckets.create`` permission ("Storage Admin" role). If the bucket
already exists, that permission is unnecessary.
To use the Google Cloud Storage backend, first `create a service account key`_
and download the JSON credentials file.
Second, find the Google Project ID that you can see in the Google Cloud
Platform console at the "Storage/Settings" menu. Export the path to the JSON
key file and the project ID as follows:
.. code-block:: console
$ export GOOGLE_PROJECT_ID=123123123123
$ export GOOGLE_APPLICATION_CREDENTIALS=$HOME/.config/gs-secret-restic-key.json
Then you can use the ``gs:`` backend type to create a new repository in the
bucket `foo` at the root path:
.. code-block:: console
$ restic -r gs:foo:/ init
enter password for new backend:
enter password again:
created restic backend bde47d6254 at gs:foo2/
[...]
The number of concurrent connections to the GCS service can be set with the
`-o gs.connections=10`. By default, at most five parallel connections are
established.
.. _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
Password prompt on Windows
**************************
At the moment, restic only supports the default Windows console
interaction. If you use emulation environments like
`MSYS2 <https://msys2.github.io/>`__ or
`Cygwin <https://www.cygwin.com/>`__, which use terminals like
``Mintty`` or ``rxvt``, you may get a password error:
You can workaround this by using a special tool called ``winpty`` (look
`here <https://sourceforge.net/p/msys2/wiki/Porting/>`__ and
`here <https://github.com/rprichard/winpty>`__ for detail information).
On MSYS2, you can install ``winpty`` as follows:
.. code-block:: console
$ pacman -S winpty
$ winpty restic -r /tmp/backup init

180
doc/040_backup.rst Normal file
View File

@@ -0,0 +1,180 @@
..
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
##########
Backing up
##########
Now we're ready to backup some data. The contents of a directory at a
specific point in time is called a "snapshot" in restic. Run the
following command and enter the repository password you chose above
again:
.. code-block:: console
$ restic -r /tmp/backup backup ~/work
enter password for repository:
scan [/home/user/work]
scanned 764 directories, 1816 files in 0:00
[0:29] 100.00% 54.732 MiB/s 1.582 GiB / 1.582 GiB 2580 / 2580 items 0 errors ETA 0:00
duration: 0:29, 54.47MiB/s
snapshot 40dc1520 saved
As you can see, restic created a backup of the directory and was pretty
fast! The specific snapshot just created is identified by a sequence of
hexadecimal characters, ``40dc1520`` in this case.
If you run the command again, restic will create another snapshot of
your data, but this time it's even faster. This is de-duplication at
work!
.. code-block:: console
$ restic -r /tmp/backup backup ~/work
enter password for repository:
using parent snapshot 40dc1520aa6a07b7b3ae561786770a01951245d2367241e71e9485f18ae8228c
scan [/home/user/work]
scanned 764 directories, 1816 files in 0:00
[0:00] 100.00% 0B/s 1.582 GiB / 1.582 GiB 2580 / 2580 items 0 errors ETA 0:00
duration: 0:00, 6572.38MiB/s
snapshot 79766175 saved
You can even backup individual files in the same repository.
.. code-block:: console
$ restic -r /tmp/backup backup ~/work.txt
scan [/home/user/work.txt]
scanned 0 directories, 1 files in 0:00
[0:00] 100.00% 0B/s 220B / 220B 1 / 1 items 0 errors ETA 0:00
duration: 0:00, 0.03MiB/s
snapshot 31f7bd63 saved
In fact several hosts may use the same repository to backup directories
and files leading to a greater de-duplication.
Please be aware that when you backup different directories (or the
directories to be saved have a variable name component like a
time/date), restic always needs to read all files and only afterwards
can compute which parts of the files need to be saved. When you backup
the same directory again (maybe with new or changed files) restic will
find the old snapshot in the repo and by default only reads those files
that are new or have been modified since the last snapshot. This is
decided based on the modify date of the file in the file system.
Now is a good time to run ``restic check`` to verify that all data
is properly stored in the repository. You should run this command regularly
to make sure the internal structure of the repository is free of errors.
You can exclude folders and files by specifying exclude-patterns. Either
specify them with multiple ``--exclude``'s or one ``--exclude-file``
.. code-block:: console
$ cat exclude
# exclude go-files
*.go
# exclude foo/x/y/z/bar foo/x/bar foo/bar
foo/**/bar
$ restic -r /tmp/backup backup ~/work --exclude=*.c --exclude-file=exclude
Patterns use `filepath.Glob <https://golang.org/pkg/path/filepath/#Glob>`__ internally,
see `filepath.Match <https://golang.org/pkg/path/filepath/#Match>`__ for syntax.
Additionally ``**`` excludes arbitrary subdirectories.
Environment-variables in exclude-files are expanded with
`os.ExpandEnv <https://golang.org/pkg/os/#ExpandEnv>`__.
By specifying the option ``--one-file-system`` you can instruct restic
to only backup files from the file systems the initially specified files
or directories reside on. For example, calling restic like this won't
backup ``/sys`` or ``/dev`` on a Linux system:
.. code-block:: console
$ restic -r /tmp/backup backup --one-file-system /
By using the ``--files-from`` option you can read the files you want to
backup from a file. This is especially useful if a lot of files have to
be backed up that are not in the same folder or are maybe pre-filtered
by other software.
or example maybe you want to backup files that have a certain filename
in them:
.. code-block:: console
$ find /tmp/somefiles | grep 'PATTERN' > /tmp/files_to_backup
You can then use restic to backup the filtered files:
.. code-block:: console
$ restic -r /tmp/backup backup --files-from /tmp/files_to_backup
Incidentally you can also combine ``--files-from`` with the normal files
args:
.. code-block:: console
$ restic -r /tmp/backup backup --files-from /tmp/files_to_backup /tmp/some_additional_file
Backing up special items
************************
**Symlinks** are archived as symlinks, ``restic`` does not follow them.
When you restore, you get the same symlink again, with the same link target
and the same timestamps.
If there is a **bind-mount** below a directory that is to be saved, restic descends into it.
**Device files** are saved and restored as device files. This means that e.g. ``/dev/sda`` is
archived as a block device file and restored as such. This also means that the content of the
corresponding disk is not read, at least not from the device file.
Reading data from stdin
***********************
Sometimes it can be nice to directly save the output of a program, e.g.
``mysqldump`` so that the SQL can later be restored. Restic supports
this mode of operation, just supply the option ``--stdin`` to the
``backup`` command like this:
.. code-block:: console
$ mysqldump [...] | restic -r /tmp/backup backup --stdin
This creates a new snapshot of the output of ``mysqldump``. You can then
use e.g. the fuse mounting option (see below) to mount the repository
and read the file.
By default, the file name ``stdin`` is used, a different name can be
specified with ``--stdin-filename``, e.g. like this:
.. code-block:: console
$ mysqldump [...] | restic -r /tmp/backup backup --stdin --stdin-filename production.sql
Tags for backup
***************
Snapshots can have one or more tags, short strings which add identifying
information. Just specify the tags for a snapshot one by one with ``--tag``:
.. code-block:: console
$ restic -r /tmp/backup backup --tag projectX --tag foo --tag bar ~/work
[...]
The tags can later be used to keep (or forget) snapshots with the ``forget``
command. The command ``tag`` can be used to modify tags on an existing
snapshot.

View File

@@ -0,0 +1,89 @@
..
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
#########################
Working with repositories
#########################
Listing all snapshots
=====================
Now, you can list all the snapshots stored in the repository:
.. code-block:: console
$ restic -r /tmp/backup snapshots
enter password for repository:
ID Date Host Tags Directory
----------------------------------------------------------------------
40dc1520 2015-05-08 21:38:30 kasimir /home/user/work
79766175 2015-05-08 21:40:19 kasimir /home/user/work
bdbd3439 2015-05-08 21:45:17 luigi /home/art
590c8fc8 2015-05-08 21:47:38 kazik /srv
9f0bc19e 2015-05-08 21:46:11 luigi /srv
You can filter the listing by directory path:
.. code-block:: console
$ restic -r /tmp/backup snapshots --path="/srv"
enter password for repository:
ID Date Host Tags Directory
----------------------------------------------------------------------
590c8fc8 2015-05-08 21:47:38 kazik /srv
9f0bc19e 2015-05-08 21:46:11 luigi /srv
Or filter by host:
.. code-block:: console
$ restic -r /tmp/backup snapshots --host luigi
enter password for repository:
ID Date Host Tags Directory
----------------------------------------------------------------------
bdbd3439 2015-05-08 21:45:17 luigi /home/art
9f0bc19e 2015-05-08 21:46:11 luigi /srv
Combining filters is also possible.
Checking a repo's integrity and consistency
===========================================
Imagine your repository is saved on a server that has a faulty hard
drive, or even worse, attackers get privileged access and modify your
backup with the intention to make you restore malicious data:
.. code-block:: console
$ sudo echo "boom" >> backup/index/d795ffa99a8ab8f8e42cec1f814df4e48b8f49129360fb57613df93739faee97
In order to detect these things, it is a good idea to regularly use the
``check`` command to test whether everything is alright, your precious
backup data is consistent and the integrity is unharmed:
.. code-block:: console
$ restic -r /tmp/backup check
Load indexes
ciphertext verification failed
Trying to restore a snapshot which has been modified as shown above will
yield the same error:
.. code-block:: console
$ restic -r /tmp/backup restore 79766175 --target /tmp/restore-work
Load indexes
ciphertext verification failed

82
doc/050_restore.rst Normal file
View File

@@ -0,0 +1,82 @@
..
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
#####################
Restoring from backup
#####################
Restoring from a snapshot
=========================
Restoring a snapshot is as easy as it sounds, just use the following
command to restore the contents of the latest snapshot to
``/tmp/restore-work``:
.. code-block:: console
$ restic -r /tmp/backup restore 79766175 --target /tmp/restore-work
enter password for repository:
restoring <Snapshot of [/home/user/work] at 2015-05-08 21:40:19.884408621 +0200 CEST> to /tmp/restore-work
Use the word ``latest`` to restore the last backup. You can also combine
``latest`` with the ``--host`` and ``--path`` filters to choose the last
backup for a specific host, path or both.
.. code-block:: console
$ restic -r /tmp/backup restore latest --target /tmp/restore-art --path "/home/art" --host luigi
enter password for repository:
restoring <Snapshot of [/home/art] at 2015-05-08 21:45:17.884408621 +0200 CEST> to /tmp/restore-art
Use ``--exclude`` and ``--include`` to restrict the restore to a subset of
files in the snapshot. For example, to restore a single file:
.. code-block:: console
$ restic -r /tmp/backup restore 79766175 --target /tmp/restore-work --include /work/foo
enter password for repository:
restoring <Snapshot of [/home/user/work] at 2015-05-08 21:40:19.884408621 +0200 CEST> to /tmp/restore-work
This will restore the file ``foo`` to ``/tmp/restore-work/work/foo``.
Restore using mount
===================
Browsing your backup as a regular file system is also very easy. First,
create a mount point such as ``/mnt/restic`` and then use the following
command to serve the repository with FUSE:
.. code-block:: console
$ mkdir /mnt/restic
$ restic -r /tmp/backup mount /mnt/restic
enter password for repository:
Now serving /tmp/backup at /mnt/restic
Don't forget to umount after quitting!
Mounting repositories via FUSE is not possible on Windows and OpenBSD.
Restic supports storage and preservation of hard links. However, since
hard links exist in the scope of a filesystem by definition, restoring
hard links from a fuse mount should be done by a program that preserves
hard links. A program that does so is ``rsync``, used with the option
--hard-links.
Printing files to stdout
========================
Sometimes it's helpful to print files to stdout so that other programs can read
the data directly. This can be achieved by using the `dump` command, like this:
.. code-block:: console
$ restic -r /tmp/backup dump latest production.sql | mysql

208
doc/060_forget.rst Normal file
View File

@@ -0,0 +1,208 @@
..
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
#########################
Removing backup snapshots
#########################
All backup space is finite, so restic allows removing old snapshots.
This can be done either manually (by specifying a snapshot ID to remove)
or by using a policy that describes which snapshots to forget. For all
remove operations, two commands need to be called in sequence:
``forget`` to remove a snapshot and ``prune`` to actually remove the
data that was referenced by the snapshot from the repository. This can
be automated with the ``--prune`` option of the ``forget`` command,
which runs ``prune`` automatically if snapshots have been removed.
It is advisable to run ``restic check`` after pruning, to make sure
you are alerted, should the internal data structures of the repository
be damaged.
Remove a single snapshot
************************
The command ``snapshots`` can be used to list all snapshots in a
repository like this:
.. code-block:: console
$ restic -r /tmp/backup snapshots
enter password for repository:
ID Date Host Tags Directory
----------------------------------------------------------------------
40dc1520 2015-05-08 21:38:30 kasimir /home/user/work
79766175 2015-05-08 21:40:19 kasimir /home/user/work
bdbd3439 2015-05-08 21:45:17 luigi /home/art
590c8fc8 2015-05-08 21:47:38 kazik /srv
9f0bc19e 2015-05-08 21:46:11 luigi /srv
In order to remove the snapshot of ``/home/art``, use the ``forget``
command and specify the snapshot ID on the command line:
.. code-block:: console
$ restic -r /tmp/backup forget bdbd3439
enter password for repository:
removed snapshot d3f01f63
Afterwards this snapshot is removed:
.. code-block:: console
$ restic -r /tmp/backup snapshots
enter password for repository:
ID Date Host Tags Directory
----------------------------------------------------------------------
40dc1520 2015-05-08 21:38:30 kasimir /home/user/work
79766175 2015-05-08 21:40:19 kasimir /home/user/work
590c8fc8 2015-05-08 21:47:38 kazik /srv
9f0bc19e 2015-05-08 21:46:11 luigi /srv
But the data that was referenced by files in this snapshot is still
stored in the repository. To cleanup unreferenced data, the ``prune``
command must be run:
.. code-block:: console
$ restic -r /tmp/backup prune
enter password for repository:
counting files in repo
building new index for repo
[0:00] 100.00% 22 / 22 files
repository contains 22 packs (8512 blobs) with 100.092 MiB bytes
processed 8512 blobs: 0 duplicate blobs, 0B duplicate
load all snapshots
find data that is still in use for 1 snapshots
[0:00] 100.00% 1 / 1 snapshots
found 8433 of 8512 data blobs still in use
will rewrite 3 packs
creating new index
[0:00] 86.36% 19 / 22 files
saved new index as 544a5084
done
Afterwards the repository is smaller.
You can automate this two-step process by using the ``--prune`` switch
to ``forget``:
.. code-block:: console
$ restic forget --keep-last 1 --prune
snapshots for host mopped, directories /home/user/work:
keep 1 snapshots:
ID Date Host Tags Directory
----------------------------------------------------------------------
4bba301e 2017-02-21 10:49:18 mopped /home/user/work
remove 1 snapshots:
ID Date Host Tags Directory
----------------------------------------------------------------------
8c02b94b 2017-02-21 10:48:33 mopped /home/user/work
1 snapshots have been removed, running prune
counting files in repo
building new index for repo
[0:00] 100.00% 37 / 37 packs
repository contains 37 packs (5521 blobs) with 151.012 MiB bytes
processed 5521 blobs: 0 duplicate blobs, 0B duplicate
load all snapshots
find data that is still in use for 1 snapshots
[0:00] 100.00% 1 / 1 snapshots
found 5323 of 5521 data blobs still in use, removing 198 blobs
will delete 0 packs and rewrite 27 packs, this frees 22.106 MiB
creating new index
[0:00] 100.00% 30 / 30 packs
saved new index as b49f3e68
done
Removing snapshots according to a policy
****************************************
Removing snapshots manually is tedious and error-prone, therefore restic
allows specifying which snapshots should be removed automatically
according to a policy. You can specify how many hourly, daily, weekly,
monthly and yearly snapshots to keep, any other snapshots are removed.
The most important command-line parameter here is ``--dry-run`` which
instructs restic to not remove anything but print which snapshots would
be removed.
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 only group snapshots by paths and
tags use ``--group-by paths,tags``. The policy is then applied to each group of
snapshots separately. This is a safety feature.
The ``forget`` command accepts the following parameters:
- ``--keep-last n`` never delete the ``n`` last (most recent) snapshots
- ``--keep-hourly n`` for the last ``n`` hours in which a snapshot was
made, keep only the last snapshot for each hour.
- ``--keep-daily n`` for the last ``n`` days which have one or more
snapshots, only keep the last one for that day.
- ``--keep-weekly n`` for the last ``n`` weeks which have one or more
snapshots, only keep the last one for that week.
- ``--keep-monthly n`` for the last ``n`` months which have one or more
snapshots, only keep the last one for that month.
- ``--keep-yearly n`` for the last ``n`` years which have one or more
snapshots, only keep the last one for that year.
- ``--keep-tag`` keep all snapshots which have all tags specified by
this option (can be specified multiple times).
Additionally, you can restrict removing snapshots to those which have a
particular hostname with the ``--hostname`` parameter, or tags with the
``--tag`` option. When multiple tags are specified, only the snapshots
which have all the tags are considered. For example, the following command
removes all but the latest snapshot of all snapshots that have the tag ``foo``:
.. code-block:: console
$ restic forget --tag foo --keep-last 1
This command removes all but the last snapshot of all snapshots that have
either the ``foo`` or ``bar`` tag set:
.. code-block:: console
$ restic forget --tag foo --tag bar --keep-last 1
To only keep the last snapshot of all snapshots with both the tag ``foo`` and
``bar`` set use:
.. code-block:: console
$ restic forget --tag foo,tag bar --keep-last 1
All the ``--keep-*`` options above only count
hours/days/weeks/months/years which have a snapshot, so those without a
snapshot are ignored.
All snapshots are evaluated counted against all matching keep-* counts. A
single snapshot on 2017-09-30 (Sun) will count as a daily, weekly and monthly.
Let's explain this with an example: Suppose you have only made a backup
on each Sunday for 12 weeks. Then ``forget --keep-daily 4`` will keep
the last four snapshots for the last four Sundays, but remove the rest.
Only counting the days which have a backup and ignore the ones without
is a safety feature: it prevents restic from removing many snapshots
when no new ones are created. If it was implemented otherwise, running
``forget --keep-daily 4`` on a Friday would remove all snapshots!
Another example: Suppose you make daily backups for 100 years. Then
``forget --keep-daily 7 --keep-weekly 5 --keep-monthly 12 --keep-yearly 75``
will keep the most recent 7 daily snapshots, then 4 (remember, 7 dailies
already include a week!) last-day-of-the-weeks and 11 or 12
last-day-of-the-months (11 or 12 depends if the 5 weeklies cross a month).
And finally 75 last-day-of-the-year snapshots. All other snapshots are
removed.

51
doc/070_encryption.rst Normal file
View File

@@ -0,0 +1,51 @@
..
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
##########
Encryption
##########
*"The design might not be perfect, but its good. Encryption is a first-class feature,
the implementation looks sane and I guess the deduplication trade-off is worth it. So… Im going to use restic for
my personal backups.*" `Filippo Valsorda`_
.. _Filippo Valsorda: https://blog.filippo.io/restic-cryptography/
**********************
Manage repository keys
**********************
The ``key`` command allows you to set multiple access keys or passwords
per repository. In fact, you can use the ``list``, ``add``, ``remove``
and ``passwd`` sub-commands to manage these keys very precisely:
.. code-block:: console
$ restic -r /tmp/backup key list
enter password for repository:
ID User Host Created
----------------------------------------------------------------------
*eb78040b username kasimir 2015-08-12 13:29:57
$ restic -r /tmp/backup key add
enter password for repository:
enter password for new key:
enter password again:
saved new key as <Key of username@kasimir, created on 2015-08-12 13:35:05.316831933 +0200 CEST>
$ restic -r backup key list
enter password for repository:
ID User Host Created
----------------------------------------------------------------------
5c657874 username kasimir 2015-08-12 13:35:05
*eb78040b username kasimir 2015-08-12 13:29:57

272
doc/080_examples.rst Normal file
View File

@@ -0,0 +1,272 @@
..
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
########
Examples
########
********************************
Setting up restic with Amazon S3
********************************
Preface
=======
This tutorial will show you how to use restic with AWS S3. It will show you how
to navigate the AWS web interface, create an S3 bucket, create a user with
access to only this bucket, and finally how to connect restic to this bucket.
Prerequisites
=============
You should already have a ``restic`` binary available on your system that you can
run. Furthermore, you should also have an account with
`AWS <https://aws.amazon.com/>`__. You will likely need to provide credit card
details for billing purposes, even if you use their
`free-tier <https://aws.amazon.com/free/>`__.
Logging into AWS
================
Point your browser to
https://console.aws.amazon.com
and log in using your AWS account. You will be presented with the AWS homepage:
.. image:: images/aws_s3/01_aws_start.png
:alt: AWS Homepage
By using the "Services" button in the upper left corder, a menu of all services
provided by AWS can be opened:
.. image:: images/aws_s3/02_aws_menu.png
:alt: AWS Services Menu
For this tutorial, the Simple Storage Service (S3), as well as Identity and
Access Management (IAM) are relevant.
Creating the bucket
===================
First, a bucket to store your backups in must be created. Using the "Services"
menu, navigate to S3. In case you already have some S3 buckets, you will see a
list of them here:
.. image:: images/aws_s3/03_buckets_list_before.png
:alt: List of S3 Buckets
Click the "Create bucket" button and choose a name and region for your new
bucket. For the purpose of this tutorial, the bucket will be named
``restic-demo`` and reside in Frankfurt. Because the bucket name space is
shared among all AWS users, the name ``restic-demo`` may not be available to
you. Be creative and choose a unique bucket name.
.. image:: images/aws_s3/04_bucket_create_start.png
:alt: Create a Bucket
It is not necessary to configure any special properties or permissions of the
bucket just yet. Therefore, just finish the wizard without making any further
changes:
.. image:: images/aws_s3/05_bucket_create_review.png
:alt: Review Bucket Creation
The newly created ``restic-demo`` bucket will now appear on the list of S3
buckets:
.. image:: images/aws_s3/06_buckets_list_after.png
:alt: List With New Bucket
Creating a user
===============
Use the "Services" menu of the AWS web interface to navigate to IAM. This will
bring you to the IAM homepage. To create a new user, click on the "Users" menu
entry on the left:
.. image:: images/aws_s3/07_iam_start.png
:alt: IAM Home Page
In case you already have set-up users with IAM before, you will see a list of
them here. Use the "Add user" button at the top to create a new user:
.. image:: images/aws_s3/08_user_list.png
:alt: IAM User List
For this tutorial, the new user will be named ``restic-demo-user``. Feel free to
choose your own name that best fits your needs. This user will only ever access
AWS through the ``restic`` program and not through the web interface. Therefore,
"Programmatic access" is selected for "Access type":
.. image:: images/aws_s3/09_user_name.png
:alt: Choose User Name and Access Type
During the next step, permissions can be assigned to the new user. To use this
user with restic, it only needs access to the ``restic-demo`` bucket. Select
"Attach existing policies directly", which will bring up a list of pre-defined
policies below. Afterwards, click the "Create policy" button to create a custom
policy:
.. image:: images/aws_s3/10_user_pre_policy.png
:alt: Assign a Policy
A new browser window or tab will open with the policy wizard. In Amazon IAM,
policies are defined as JSON documents. For this tutorial, the "Policy
Generator" will be used to generate a policy file using a web interface:
.. image:: images/aws_s3/11_policy_start.png
:alt: Create a New Policy
After invoking the policy generator, you will be presented with a user
interface to generate individual permission statements. For restic to work, two
such statements must be created. The first statement is set up as follows:
.. code::
Effect: Allow
Service: Amazon S3
Actions: DeleteObject, GetObject, PutObject
Resource: arn:aws:s3:::restic-demo/*
This statement allows restic to create, read and delete objects inside the S3
bucket named ``restic-demo``. Adjust the bucket's name to the name of the bucket
you created earlier. Using the "Add Statement" button, this statement can be
saved. Now a second statement is created:
.. code::
Effect: Allow
Service: Amazon S3
Actions: ListBucket
Resource: arn:aws:s3:::restic-demo
Again, substitute ``restic-demo`` with the actual name of your bucket. Note that,
unlike before, there is no ``/*`` after the bucket name. This statement allows
restic to list the objects stored in the ``restic-demo`` bucket. Again, use "Add
Statement" to save this statement. The policy creator interface should now
look as follows:
.. image:: images/aws_s3/12_policy_permissions_done.png
:alt: Policy Creator With Two Statements
Continue to the next step and enter a name and description for this policy. For
this tutorial, the policy will be named ``restic-demo-policy``. In this step you
can also examine the JSON document created by the policy generator. Click
"Create Policy" to finish the process:
.. image:: images/aws_s3/13_policy_review.png
:alt: Policy Review
Go back to the browser window or tab where you were previously creating the new
user. Click the button labeled "Refresh" above the list of policies to make
sure the newly created policy is available to you. Afterwards, use the search
function to search for the ``restic-demo-policy``. Select this policy using the
checkbox on the left. Then, continue to the next step.
.. image:: images/aws_s3/14_user_attach_policy.png
:alt: Attach Policy to User
The next page will present an overview of the user account that is about to be
created. If everything looks good, click "Create user" to complete the process:
.. image:: images/aws_s3/15_user_review.png
:alt: User Creation Review
After the user has been created, its access credentials will be displayed. They
consist of the "Access key ID" (think user name), and the "Secret access key"
(think password). Copy these down to a safe place.
.. image:: images/aws_s3/16_user_created.png
:alt: User Credentials
You have now completed the configuration in AWS. Feel free to close your web
browser now.
Initializing the restic repository
==================================
Open a terminal and make sure you have the ``restic`` binary ready. First, choose
a password to encrypt your backups with. In this tutorial, ``apg`` is used for
this purpose:
.. code-block:: console
$ apg -a 1 -m 32 -n 1 -M NCL
I9n7G7G0ZpDWA3GOcJbIuwQCGvGUBkU5
Note this password somewhere safe along with your AWS credentials. Next, the
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 and your user's API credentials.
.. code-block:: console
$ unset HISTFILE
$ 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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"
$ export RESTIC_PASSWORD="I9n7G7G0ZpDWA3GOcJbIuwQCGvGUBkU5"
After the environment is set up, restic may be called to initialize the
repository:
.. code-block:: console
$ ./restic init
created restic backend b5c661a86a at s3:https://s3.amazonaws.com/restic-demo
Please note that knowledge of your password is required to access
the repository. Losing your password means that your data is
irrecoverably lost.
restic is now ready to be used with AWS S3. Try to create a backup:
.. code-block:: console
$ dd if=/dev/urandom bs=1M count=10 of=test.bin
10+0 records in
10+0 records out
10485760 bytes (10 MB, 10 MiB) copied, 0,0891322 s, 118 MB/s
$ ./restic backup test.bin
scan [/home/philip/restic-demo/test.bin]
scanned 0 directories, 1 files in 0:00
[0:04] 100.00% 2.500 MiB/s 10.000 MiB / 10.000 MiB 1 / 1 items ... ETA 0:00
duration: 0:04, 2.47MiB/s
snapshot 10fdbace saved
$ ./restic snapshots
ID Date Host Tags Directory
----------------------------------------------------------------------
10fdbace 2017-03-26 16:41:50 blackbox /home/philip/restic-demo/test.bin
A snapshot was created and stored in the S3 bucket. This snapshot may now be
restored:
.. code-block:: console
$ mkdir restore
$ ./restic restore 10fdbace --target restore
restoring <Snapshot 10fdbace of [/home/philip/restic-demo/test.bin] at 2017-03-26 16:41:50.201418102 +0200 CEST by philip@blackbox> to restore
$ ls restore/
test.bin
The snapshot was successfully restored. This concludes the tutorial.

140
doc/090_participating.rst Normal file
View File

@@ -0,0 +1,140 @@
..
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
#############
Participating
#############
*********
Debugging
*********
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
$ DEBUG_LOG=/tmp/restic-debug.log restic backup ~/work
If you suspect that there is a bug, you can have a look at the debug
log. Please be aware that the debug log might contain sensitive
information such as file and directory names.
The debug log will always contain all log messages restic generates. You
can also instruct restic to print some or all debug messages to stderr.
These can also be limited to e.g. a list of source files or a list of
patterns for function names. The patterns are globbing patterns (see the
documentation for `path.Glob <https://golang.org/pkg/path/#Glob>`__), multiple
patterns are separated by commas. Patterns are case sensitive.
Printing all log messages to the console can be achieved by setting the
file filter to ``*``:
.. code-block:: console
$ DEBUG_FILES=* restic check
If you want restic to just print all debug log messages from the files
``main.go`` and ``lock.go``, set the environment variable
``DEBUG_FILES`` like this:
.. code-block:: console
$ DEBUG_FILES=main.go,lock.go restic check
The following command line instructs restic to only print debug
statements originating in functions that match the pattern ``*unlock*``
(case sensitive):
.. code-block:: console
$ DEBUG_FUNCS=*unlock* restic check
************
Contributing
************
Contributions are welcome! Please **open an issue first** (or add a
comment to an existing issue) if you plan to work on any code or add a
new feature. This way, duplicate work is prevented and we can discuss
your ideas and design first.
More information and a description of the development environment can be
found in `CONTRIBUTING.md <https://github.com/restic/restic/blob/master/CONTRIBUTING.md>`__.
A document describing the design of restic and the data structures stored on the
back end is contained in `Design <https://restic.readthedocs.io/en/latest/design.html>`__.
If you'd like to start contributing to restic, but don't know exactly
what do to, have a look at this great article by Dave Cheney:
`Suggestions for contributing to an Open Source
project <http://dave.cheney.net/2016/03/12/suggestions-for-contributing-to-an-open-source-project>`__
A few issues have been tagged with the label ``help wanted``, you can
start looking at those:
https://github.com/restic/restic/labels/help%20wanted
********
Security
********
**Important**: If you discover something that you believe to be a
possible critical security problem, please do *not* open a GitHub issue
but send an email directly to alexander@bumpern.de. If possible, please
encrypt your email using the following PGP key
(`0x91A6868BD3F7A907 <https://pgp.mit.edu/pks/lookup?op=get&search=0xCF8F18F2844575973F79D4E191A6868BD3F7A907>`__):
::
pub 4096R/91A6868BD3F7A907 2014-11-01
Key fingerprint = CF8F 18F2 8445 7597 3F79 D4E1 91A6 868B D3F7 A907
uid Alexander Neumann <alexander@bumpern.de>
sub 4096R/D5FC2ACF4043FDF1 2014-11-01
*************
Compatibility
*************
Backward compatibility for backups is important so that our users are
always able to restore saved data. Therefore restic follows `Semantic
Versioning <http://semver.org>`__ to clearly define which versions are
compatible. The repository and data structures contained therein are
considered the "Public API" in the sense of Semantic Versioning. This
goes for all released versions of restic, this may not be the case for
the master branch.
We guarantee backward compatibility of all repositories within one major
version; as long as we do not increment the major version, data can be
read and restored. We strive to be fully backward compatible to all
prior versions.
**********************
Building documentation
**********************
The restic documentation is built with `Sphinx <http://sphinx-doc.org>`__,
therefore building it locally requires a recent Python version and requirements listed in ``doc/requirements.txt``.
This example will guide you through the process using `virtualenv <https://virtualenv.pypa.io>`__:
::
$ virtualenv venv # create virtual python environment
$ source venv/bin/activate # activate the virtual environment
$ cd doc
$ pip install -r requirements.txt # install dependencies
$ make html # build html documentation
$ # open _build/html/index.html with your favorite browser

763
doc/100_references.rst Normal file
View File

@@ -0,0 +1,763 @@
..
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
##########
References
##########
******
Design
******
Terminology
===========
This section introduces terminology used in this document.
*Repository*: All data produced during a backup is sent to and stored in
a repository in a structured form, for example in a file system
hierarchy with several subdirectories. A repository implementation must
be able to fulfill a number of operations, e.g. list the contents.
*Blob*: A Blob combines a number of data bytes with identifying
information like the SHA-256 hash of the data and its length.
*Pack*: A Pack combines one or more Blobs, e.g. in a single file.
*Snapshot*: A Snapshot stands for the state of a file or directory that
has been backed up at some point in time. The state here means the
content and meta data like the name and modification time for the file
or the directory and its contents.
*Storage ID*: A storage ID is the SHA-256 hash of the content stored in
the repository. This ID is required in order to load the file from the
repository.
Repository Format
=================
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. This allows accessing and even writing to the
repository with multiple clients in parallel. Only the delete 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
the file is the lower case hexadecimal representation of the storage ID,
which is the SHA-256 hash of the file's contents. This allows for easy
verification of files for accidental modifications, like disk read
errors, by simply running the program ``sha256sum`` on the file and
comparing its output to the file name. If the prefix of a filename is
unique amongst all the other files in the same directory, the prefix may
be used instead of the complete filename.
Apart from the files stored within the ``keys`` directory, all files are
encrypted with AES-256 in counter mode (CTR). The integrity of the
encrypted data is secured by a Poly1305-AES message authentication code
(sometimes also referred to as a "signature").
In the first 16 bytes of each encrypted file the initialisation vector
(IV) is stored. It is followed by the encrypted data and completed by
the 16 byte MAC. The format is: ``IV || CIPHERTEXT || MAC``. The
complete encryption overhead is 32 bytes. For each file, a new random IV
is selected.
The file ``config`` is encrypted this way and contains a JSON document
like the following:
.. code:: json
{
"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. 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.
The basic layout of a repository stored in a ``local`` or ``sftp``
backend is shown here:
::
/tmp/restic-repo
├── config
├── data
│ ├── 21
│ │ └── 2159dd48f8a24f33c307b750592773f8b71ff8d11452132a7b2e2a6a01611be1
│ ├── 32
│ │ └── 32ea976bc30771cebad8285cd99120ac8786f9ffd42141d452458089985043a5
│ ├── 59
│ │ └── 59fe4bcde59bd6222eba87795e35a90d82cd2f138a27b6835032b7b58173a426
│ ├── 73
│ │ └── 73d04e6125cf3c28a299cc2f3cca3b78ceac396e4fcf9575e34536b26782413c
│ [...]
├── index
│ ├── c38f5fb68307c6a3e3aa945d556e325dc38f5fb68307c6a3e3aa945d556e325d
│ └── ca171b1b7394d90d330b265d90f506f9984043b342525f019788f97e745c71fd
├── keys
│ └── b02de829beeb3c01a63e6b25cbd421a98fef144f03b9a02e46eff9e2ca3f0bd7
├── locks
├── snapshots
│ └── 22a5af1bdc6e616f8a29579458c49627e01b32210d09adb288d1ecda7c5711ec
└── tmp
A local repository can be initialized with the ``restic init`` command,
e.g.:
.. code-block:: console
$ restic -r /tmp/restic-repo init
The local and sftp backends will auto-detect and accept all layouts described
in the following sections, so that remote repositories mounted locally e.g. via
fuse can be accessed. The layout auto-detection can be overridden by specifying
the option ``-o local.layout=default``, valid values are ``default`` and
``s3legacy``. The option for the sftp backend is named ``sftp.layout``, for the
s3 backend ``s3.layout``.
S3 Legacy Layout
----------------
Unfortunately during development the AWS S3 backend uses slightly different
paths (directory names use singular instead of plural for ``key``,
``lock``, and ``snapshot`` files), and the data files are stored directly below
the ``data`` directory. The S3 Legacy repository layout looks like this:
::
/config
/data
├── 2159dd48f8a24f33c307b750592773f8b71ff8d11452132a7b2e2a6a01611be1
├── 32ea976bc30771cebad8285cd99120ac8786f9ffd42141d452458089985043a5
├── 59fe4bcde59bd6222eba87795e35a90d82cd2f138a27b6835032b7b58173a426
├── 73d04e6125cf3c28a299cc2f3cca3b78ceac396e4fcf9575e34536b26782413c
[...]
/index
├── c38f5fb68307c6a3e3aa945d556e325dc38f5fb68307c6a3e3aa945d556e325d
└── ca171b1b7394d90d330b265d90f506f9984043b342525f019788f97e745c71fd
/key
└── b02de829beeb3c01a63e6b25cbd421a98fef144f03b9a02e46eff9e2ca3f0bd7
/lock
/snapshot
└── 22a5af1bdc6e616f8a29579458c49627e01b32210d09adb288d1ecda7c5711ec
The S3 backend understands and accepts both forms, new backends are
always created with the default layout for compatibility reasons.
Pack Format
===========
All files in the repository except Key and Pack files just contain raw
data, stored as ``IV || Ciphertext || MAC``. Pack files may contain one
or more Blobs of data.
A Pack's structure is as follows:
::
EncryptedBlob1 || ... || EncryptedBlobN || EncryptedHeader || Header_Length
At the end of the Pack file is a header, which describes the content.
The header is encrypted and authenticated. ``Header_Length`` is the
length of the encrypted header encoded as a four byte integer in
little-endian encoding. Placing the header at the end of a file allows
writing the blobs in a continuous stream as soon as they are read during
the backup phase. This reduces code complexity and avoids having to
re-write a file once the pack is complete and the content and length of
the header is known.
All the blobs (``EncryptedBlob1``, ``EncryptedBlobN`` etc.) are
authenticated and encrypted independently. This enables repository
reorganisation without having to touch the encrypted Blobs. In addition
it also allows efficient indexing, for only the header needs to be read
in order to find out which Blobs are contained in the Pack. Since the
header is authenticated, authenticity of the header can be checked
without having to read the complete Pack.
After decryption, a Pack's header consists of the following elements:
::
Type_Blob1 || Length(EncryptedBlob1) || Hash(Plaintext_Blob1) ||
[...]
Type_BlobN || Length(EncryptedBlobN) || Hash(Plaintext_Blobn) ||
This is enough to calculate the offsets for all the Blobs in the Pack.
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:
+--------+-----------+
| Type | Meaning |
+========+===========+
| 0 | data |
+--------+-----------+
| 1 | tree |
+--------+-----------+
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.
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 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
{
"supersedes": [
"ed54ae36197f4745ebc4b54d10e0f623eaaaedd03013eb7ae90df881b7781452"
],
"packs": [
{
"id": "73d04e6125cf3c28a299cc2f3cca3b78ceac396e4fcf9575e34536b26782413c",
"blobs": [
{
"id": "3ec79977ef0cf5de7b08cd12b874cd0f62bbaf7f07f3497a5b1bbcc8cb39b1ce",
"type": "data",
"offset": 0,
"length": 25
},{
"id": "9ccb846e60d90d4eb915848add7aa7ea1e4bbabfc60e573db9f7bfb2789afbae",
"type": "tree",
"offset": 38,
"length": 100
},
{
"id": "d3dc577b4ffd38cc4b32122cabf8655a0223ed22edfd93b353dc0c3f2b0fdf66",
"type": "data",
"offset": 150,
"length": 123
}
]
}, [...]
]
}
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 field ``supersedes`` lists the storage IDs of index files that have
been replaced with the current index file. This happens when index files
are repacked, for example when old snapshots are removed and Packs are
recombined.
There may be an arbitrary number of index files, containing information
on non-disjoint sets of Packs. The number of packs described in a single
file is chosen so that the file size is kept below 8 MiB.
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
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
Poly1305-AES message-authentication
code <http://cr.yp.to/mac/poly1305-20050329.pdf>`__ by Dan Bernstein.
The data is then encrypted with AES-256 and afterwards a message
authentication code (MAC) is computed over the ciphertext, everything is
then stored as IV \|\| CIPHERTEXT \|\| MAC.
The directory ``keys`` contains key files. These are simple JSON
documents which contain all data that is needed to derive the
repository's master encryption and message authentication keys from a
user's password. The JSON document from the repository can be
pretty-printed for example by using the Python module ``json``
(shortened to increase readability):
::
$ python -mjson.tool /tmp/restic-repo/keys/b02de82*
{
"hostname": "kasimir",
"username": "fd0"
"kdf": "scrypt",
"N": 65536,
"r": 8,
"p": 1,
"created": "2015-01-02T18:10:13.48307196+01:00",
"data": "tGwYeKoM0C4j4/9DFrVEmMGAldvEn/+iKC3te/QE/6ox/V4qz58FUOgMa0Bb1cIJ6asrypCx/Ti/pRXCPHLDkIJbNYd2ybC+fLhFIJVLCvkMS+trdywsUkglUbTbi+7+Ldsul5jpAj9vTZ25ajDc+4FKtWEcCWL5ICAOoTAxnPgT+Lh8ByGQBH6KbdWabqamLzTRWxePFoYuxa7yXgmj9A==",
"salt": "uW4fEI1+IOzj7ED9mVor+yTSJFd68DGlGOeLgJELYsTU5ikhG/83/+jGd4KKAaQdSrsfzrdOhAMftTSih5Ux6w==",
}
When the repository is opened by restic, the user is prompted for the
repository password. This is then used with ``scrypt``, a key derivation
function (KDF), and the supplied parameters (``N``, ``r``, ``p`` and
``salt``) to derive 64 key bytes. The first 32 bytes are used as the
encryption key (for AES-256) and the last 32 bytes are used as the
message authentication key (for Poly1305-AES). These last 32 bytes are
divided into a 16 byte AES key ``k`` followed by 16 bytes of secret key
``r``. The key ``r`` is then masked for use with Poly1305 (see the paper
for details).
Those keys are used to authenticate and decrypt the bytes contained in
the JSON field ``data`` with AES-256 and Poly1305-AES as if they were
any other blob (after removing the Base64 encoding). If the
password is incorrect or the key file has been tampered with, the
computed MAC will not match the last 16 bytes of the data, and restic
exits with an error. Otherwise, the data yields a JSON document
which contains the master encryption and message authentication keys for
this repository (encoded in Base64). The command
``restic cat masterkey`` can be used as follows to decrypt and
pretty-print the master key:
.. code-block:: console
$ restic -r /tmp/restic-repo cat masterkey
{
"mac": {
"k": "evFWd9wWlndL9jc501268g==",
"r": "E9eEDnSJZgqwTOkDtOp+Dw=="
},
"encrypt": "UQCqa0lKZ94PygPxMRqkePTZnHRYh1k1pX2k2lM2v3Q=",
}
All data in the repository is encrypted and authenticated with these
master keys. For encryption, the AES-256 algorithm in Counter mode is
used. For message authentication, Poly1305-AES is used as described
above.
A repository can have several different passwords, with a key file for
each. This way, the password can be changed without having to re-encrypt
all data.
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 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.
The command ``restic cat snapshot`` can be used as follows to decrypt
and pretty-print the contents of a snapshot file:
.. code-block:: console
$ restic -r /tmp/restic-repo cat snapshot 251c2e58
enter password for repository:
{
"time": "2015-01-02T18:10:50.895208559+01:00",
"tree": "2da81727b6585232894cfbb8f8bdab8d1eccd3d8f7c92bc934d62e62e618ffdf",
"dir": "/tmp/testdata",
"hostname": "kasimir",
"username": "fd0",
"uid": 1000,
"gid": 100,
"tags": [
"NL"
]
}
Here it can be seen that this snapshot represents the contents of the
directory ``/tmp/testdata``. The most important field is ``tree``. When
the meta data (e.g. the tags) of a snapshot change, the snapshot needs
to be re-encrypted and saved. This will change the storage ID, so in
order to relate these seemingly different snapshots, a field
``original`` is introduced which contains the ID of the original
snapshot, e.g. after adding the tag ``DE`` to the snapshot above it
becomes:
.. code-block:: console
$ restic -r /tmp/restic-repo cat snapshot 22a5af1b
enter password for repository:
{
"time": "2015-01-02T18:10:50.895208559+01:00",
"tree": "2da81727b6585232894cfbb8f8bdab8d1eccd3d8f7c92bc934d62e62e618ffdf",
"dir": "/tmp/testdata",
"hostname": "kasimir",
"username": "fd0",
"uid": 1000,
"gid": 100,
"tags": [
"NL",
"DE"
],
"original": "251c2e5841355f743f9d4ffd3260bee765acee40a6229857e32b60446991b837"
}
Once introduced, the ``original`` field is not modified when the
snapshot's meta data is changed again.
All content within a restic repository is referenced according to its
SHA-256 hash. Before saving, each file is split into variable sized
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
header of all data Blobs can be read.
Trees and Data
==============
A snapshot references a tree by the SHA-256 hash of the JSON string
representation of its contents. Trees and data are saved in pack files
in a subdirectory of the directory ``data``.
The command ``restic cat blob`` can be used to inspect the tree
referenced above (piping the output of the command to ``jq .`` so that
the JSON is indented):
.. code-block:: console
$ restic -r /tmp/restic-repo cat blob 2da81727b6585232894cfbb8f8bdab8d1eccd3d8f7c92bc934d62e62e618ffdf | jq .
enter password for repository:
{
"nodes": [
{
"name": "testdata",
"type": "dir",
"mode": 493,
"mtime": "2014-12-22T14:47:59.912418701+01:00",
"atime": "2014-12-06T17:49:21.748468803+01:00",
"ctime": "2014-12-22T14:47:59.912418701+01:00",
"uid": 1000,
"gid": 100,
"user": "fd0",
"inode": 409704562,
"content": null,
"subtree": "b26e315b0988ddcd1cee64c351d13a100fedbc9fdbb144a67d1b765ab280b4dc"
}
]
}
A tree contains a list of entries (in the field ``nodes``) which contain
meta data like a name and timestamps. When the entry references a
directory, the field ``subtree`` contains the plain text ID of another
tree object.
When the command ``restic cat blob`` is used, the plaintext ID is needed
to print a tree. The tree referenced above can be dumped as follows:
.. code-block:: console
$ restic -r /tmp/restic-repo cat blob b26e315b0988ddcd1cee64c351d13a100fedbc9fdbb144a67d1b765ab280b4dc
enter password for repository:
{
"nodes": [
{
"name": "testfile",
"type": "file",
"mode": 420,
"mtime": "2014-12-06T17:50:23.34513538+01:00",
"atime": "2014-12-06T17:50:23.338468713+01:00",
"ctime": "2014-12-06T17:50:23.34513538+01:00",
"uid": 1000,
"gid": 100,
"user": "fd0",
"inode": 416863351,
"size": 1234,
"links": 1,
"content": [
"50f77b3b4291e8411a027b9f9b9e64658181cc676ce6ba9958b95f268cb1109d"
]
},
[...]
]
}
This tree contains a file entry. This time, the ``subtree`` field is not
present and the ``content`` field contains a list with one plain text
SHA-256 hash.
The command ``restic cat blob`` can also be used to extract and decrypt
data given a plaintext ID, e.g. for the data mentioned above:
.. code-block:: console
$ restic -r /tmp/restic-repo cat blob 50f77b3b4291e8411a027b9f9b9e64658181cc676ce6ba9958b95f268cb1109d | sha256sum
enter password for repository:
50f77b3b4291e8411a027b9f9b9e64658181cc676ce6ba9958b95f268cb1109d -
As can be seen from the output of the program ``sha256sum``, the hash
matches the plaintext hash from the map included in the tree above, so
the correct data has been returned.
Locks
=====
The restic repository structure is designed in a way that allows
parallel access of multiple instance of restic and even parallel writes.
However, there are some functions that work more efficient or even
require exclusive access of the repository. In order to implement these
functions, restic processes are required to create a lock on the
repository before doing anything.
Locks come in two types: Exclusive and non-exclusive locks. At most one
process can have an exclusive lock on the repository, and during that
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 encrypted and authenticated the same way as
other files in the repository and contains the following JSON structure:
.. code:: json
{
"time": "2015-06-27T12:18:51.759239612+02:00",
"exclusive": false,
"hostname": "kasimir",
"username": "fd0",
"pid": 13607,
"uid": 1000,
"gid": 100
}
The field ``exclusive`` defines the type of lock. When a new lock is to
be created, restic checks all locks in the repository. When a lock is
found, it is tested if the lock is stale, which is the case for locks
with timestamps older than 30 minutes. If the lock was created on the
same machine, even for younger locks it is tested whether the process is
still alive by sending a signal to it. If that fails, restic assumes
that the process is dead and considers the lock to be stale.
When a new lock is to be created and no other conflicting locks are
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.
Backups and Deduplication
=========================
For creating a backup, restic scans the source directory for all files,
sub-directories and other entries. The data from each file is split into
variable length Blobs cut at offsets defined by a sliding window of 64
byte. The implementation uses Rabin Fingerprints for implementing this
Content Defined Chunking (CDC). An irreducible polynomial is selected at
random and saved in the file ``config`` when a repository is
initialized, so that watermark attacks are much harder.
Files smaller than 512 KiB are not split, Blobs are of 512 KiB to 8 MiB
in size. The implementation aims for 1 MiB Blob size on average.
For modified files, only modified Blobs have to be saved in a subsequent
backup. This even works if bytes are inserted or removed at arbitrary
positions within the file.
Threat Model
============
The design goals for restic include being able to securely store backups
in a location that is not completely trusted, e.g. a shared system where
others can potentially access the files or (in the case of the system
administrator) even modify or delete them.
General assumptions:
- The host system a backup is created on is trusted. This is the most
basic requirement, and essential for creating trustworthy backups.
The restic backup program guarantees the following:
- Accessing the unencrypted content of stored files and metadata should
not be possible without a password for the repository. Everything
except the metadata included for informational purposes in the key
files is encrypted and authenticated.
- Modifications (intentional or unintentional) can be detected
automatically on several layers:
1. For all accesses of data stored in the repository it is checked
whether the cryptographic hash of the contents matches the storage
ID (the file's name). This way, modifications (bad RAM, broken
harddisk) can be detected easily.
2. Before decrypting any data, the MAC on the encrypted data is
checked. If there has been a modification, the MAC check will
fail. This step happens even before the data is decrypted, so data
that has been tampered with is not decrypted at all.
However, the restic backup program is not designed to protect against
attackers deleting files at the storage location. There is nothing that
can be done about this. If this needs to be guaranteed, get a secure
location without any access from third parties. If you assume that
attackers have write access to your files at the storage location,
attackers are able to figure out (e.g. based on the timestamps of the
stored files) which files belong to what snapshot. When only these files
are deleted, the particular snapshot vanished and all snapshots
depending on data that has been added in the snapshot cannot be restored
completely. Restic is not designed to detect this attack.
Local Cache
===========
In order to speed up certain operations, restic manages a local cache of data.
This document describes the data structures for the local cache with version 1.
Versions
--------
The cache directory is selected according to the `XDG base dir specification
<http://standards.freedesktop.org/basedir-spec/basedir-spec-latest.html>`__.
Each repository has its own cache sub-directory, consting of the repository ID
which is chosen at ``init``. All cache directories for different repos are
independent of each other.
The cache dir for a repo contains a file named ``version``, which contains a
single ASCII integer line that stands for the current version of the cache. If
a lower version number is found the cache is recreated with the current
version. If a higher version number is found the cache is ignored and left as
is.
Snapshots and Indexes
---------------------
Snapshot, Data and Index files are cached in the sub-directories ``snapshots``,
``data`` and ``index``, as read from the repository.
************
REST Backend
************
Restic can interact with HTTP Backend that respects the following REST
API. The following values are valid for ``{type}``: ``data``, ``keys``,
``locks``, ``snapshots``, ``index``, ``config``. ``{path}`` is a path to
the repository, so that multiple different repositories can be accessed.
The default path is ``/``.
POST {path}?create=true
=======================
This request is used to initially create a new repository. The server
responds with "200 OK" if the repository structure was created
successfully or already exists, otherwise an error is returned.
DELETE {path}
=============
Deletes the repository on the server side. The server responds with "200
OK" if the repository was successfully removed. If this function is not
implemented the server returns "501 Not Implemented", if this it is
denied by the server it returns "403 Forbidden".
HEAD {path}/config
==================
Returns "200 OK" if the repository has a configuration, an HTTP error
otherwise.
GET {path}/config
=================
Returns the content of the configuration file if the repository has a
configuration, an HTTP error otherwise.
Response format: binary/octet-stream
POST {path}/config
==================
Returns "200 OK" if the configuration of the request body has been
saved, an HTTP error otherwise.
GET {path}/{type}/
==================
Returns a JSON array containing the names of all the blobs stored for a
given type.
Response format: JSON
HEAD {path}/{type}/{name}
=========================
Returns "200 OK" if the blob with the given name and type is stored in
the repository, "404 not found" otherwise. If the blob exists, the HTTP
header ``Content-Length`` is set to the file size.
GET {path}/{type}/{name}
========================
Returns the content of the blob with the given name and type if it is
stored in the repository, "404 not found" otherwise.
If the request specifies a partial read with a Range header field, then
the status code of the response is 206 instead of 200 and the response
only contains the specified range.
Response format: binary/octet-stream
POST {path}/{type}/{name}
=========================
Saves the content of the request body as a blob with the given name and
type, an HTTP error otherwise.
Request format: binary/octet-stream
DELETE {path}/{type}/{name}
===========================
Returns "200 OK" if the blob with the given name and type has been
deleted from the repository, an HTTP error otherwise.
*****
Talks
*****
The following talks will be or have been given about restic:
- 2016-01-31: Lightning Talk at the Go Devroom at FOSDEM 2016,
Brussels, Belgium
- 2016-01-29: `restic - Backups mal
richtig <https://media.ccc.de/v/c4.openchaos.2016.01.restic>`__:
Public lecture in German at `CCC Cologne
e.V. <https://koeln.ccc.de>`__ in Cologne, Germany
- 2015-08-23: `A Solution to the Backup
Inconvenience <https://programm.froscon.de/2015/events/1515.html>`__:
Lecture at `FROSCON 2015 <https://www.froscon.de>`__ in Bonn, Germany
- 2015-02-01: `Lightning Talk at FOSDEM
2015 <https://www.youtube.com/watch?v=oM-MfeflUZ8&t=11m40s>`__: A
short introduction (with slightly outdated command line)
- 2015-01-27: `Talk about restic at CCC
Aachen <https://videoag.fsmpi.rwth-aachen.de/?view=player&lectureid=4442#content>`__
(in German)

View File

@@ -1 +0,0 @@
design.rst

1193
doc/bash-completion.sh Normal file

File diff suppressed because it is too large Load Diff

26
doc/cache.rst Normal file
View File

@@ -0,0 +1,26 @@
Local Cache
===========
In order to speed up certain operations, restic manages a local cache of data.
This document describes the data structures for the local cache with version 1.
Versions
--------
The cache directory is selected according to the `XDG base dir specification
<http://standards.freedesktop.org/basedir-spec/basedir-spec-latest.html>`__.
Each repository has its own cache sub-directory, consting of the repository ID
which is chosen at ``init``. All cache directories for different repos are
independent of each other.
The cache dir for a repo contains a file named ``version``, which contains a
single ASCII integer line that stands for the current version of the cache. If
a lower version number is found the cache is recreated with the current
version. If a higher version number is found the cache is ignored and left as
is.
Snapshots and Indexes
---------------------
Snapshot, Data and Index files are cached in the sub-directories ``snapshots``,
``data`` and ``index``, as read from the repository.

View File

@@ -19,7 +19,7 @@ import os
# Add any Sphinx extension module names here, as strings. They can be
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
# ones.
extensions = []
extensions = ['sphinx.ext.extlinks']
# Add any paths that contain templates here, relative to this directory.
templates_path = ['_templates']
@@ -104,3 +104,7 @@ html_static_path = ['_static']
# Output file base name for HTML help builder.
htmlhelp_basename = 'resticdoc'
extlinks = {
'issue': ('https://github.com/restic/restic/issues/%s', '#'),
}

View File

@@ -1,610 +0,0 @@
Design
======
Terminology
-----------
This section introduces terminology used in this document.
*Repository*: All data produced during a backup is sent to and stored in
a repository in a structured form, for example in a file system
hierarchy with several subdirectories. A repository implementation must
be able to fulfill a number of operations, e.g. list the contents.
*Blob*: A Blob combines a number of data bytes with identifying
information like the SHA-256 hash of the data and its length.
*Pack*: A Pack combines one or more Blobs, e.g. in a single file.
*Snapshot*: A Snapshot stands for the state of a file or directory that
has been backed up at some point in time. The state here means the
content and meta data like the name and modification time for the file
or the directory and its contents.
*Storage ID*: A storage ID is the SHA-256 hash of the content stored in
the repository. This ID is required in order to load the file from the
repository.
Repository Format
-----------------
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. This allows accessing and even writing to the
repository with multiple clients in parallel. Only the delete 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
the file is the lower case hexadecimal representation of the storage ID,
which is the SHA-256 hash of the file's contents. This allows for easy
verification of files for accidental modifications, like disk read
errors, by simply running the program ``sha256sum`` on the file and
comparing its output to the file name. If the prefix of a filename is
unique amongst all the other files in the same directory, the prefix may
be used instead of the complete filename.
Apart from the files stored within the ``keys`` directory, all files are
encrypted with AES-256 in counter mode (CTR). The integrity of the
encrypted data is secured by a Poly1305-AES message authentication code
(sometimes also referred to as a "signature").
In the first 16 bytes of each encrypted file the initialisation vector
(IV) is stored. It is followed by the encrypted data and completed by
the 16 byte MAC. The format is: ``IV || CIPHERTEXT || MAC``. The
complete encryption overhead is 32 bytes. For each file, a new random IV
is selected.
The file ``config`` is encrypted this way and contains a JSON document
like the following:
.. code:: json
{
"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. 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.
The basic layout of a repository stored in a ``local`` or ``sftp``
backend is shown here:
::
/tmp/restic-repo
├── config
├── data
│ ├── 21
│ │ └── 2159dd48f8a24f33c307b750592773f8b71ff8d11452132a7b2e2a6a01611be1
│ ├── 32
│ │ └── 32ea976bc30771cebad8285cd99120ac8786f9ffd42141d452458089985043a5
│ ├── 59
│ │ └── 59fe4bcde59bd6222eba87795e35a90d82cd2f138a27b6835032b7b58173a426
│ ├── 73
│ │ └── 73d04e6125cf3c28a299cc2f3cca3b78ceac396e4fcf9575e34536b26782413c
│ [...]
├── index
│ ├── c38f5fb68307c6a3e3aa945d556e325dc38f5fb68307c6a3e3aa945d556e325d
│ └── ca171b1b7394d90d330b265d90f506f9984043b342525f019788f97e745c71fd
├── keys
│ └── b02de829beeb3c01a63e6b25cbd421a98fef144f03b9a02e46eff9e2ca3f0bd7
├── locks
├── snapshots
│ └── 22a5af1bdc6e616f8a29579458c49627e01b32210d09adb288d1ecda7c5711ec
└── tmp
A local repository can be initialized with the ``restic init`` command,
e.g.:
.. code-block:: console
$ restic -r /tmp/restic-repo init
The local and sftp backends will auto-detect and accept all layouts described
in the following sections, so that remote repositories mounted locally e.g. via
fuse can be accessed. The layout auto-detection can be overridden by specifying
the option ``-o local.layout=default``, valid values are ``default`` and
``s3legacy``. The option for the sftp backend is named ``sftp.layout``, for the
s3 backend ``s3.layout``.
S3 Legacy Layout
~~~~~~~~~~~~~~~~
Unfortunately during development the AWS S3 backend uses slightly different
paths (directory names use singular instead of plural for ``key``,
``lock``, and ``snapshot`` files), and the data files are stored directly below
the ``data`` directory. The S3 Legacy repository layout looks like this:
::
/config
/data
├── 2159dd48f8a24f33c307b750592773f8b71ff8d11452132a7b2e2a6a01611be1
├── 32ea976bc30771cebad8285cd99120ac8786f9ffd42141d452458089985043a5
├── 59fe4bcde59bd6222eba87795e35a90d82cd2f138a27b6835032b7b58173a426
├── 73d04e6125cf3c28a299cc2f3cca3b78ceac396e4fcf9575e34536b26782413c
[...]
/index
├── c38f5fb68307c6a3e3aa945d556e325dc38f5fb68307c6a3e3aa945d556e325d
└── ca171b1b7394d90d330b265d90f506f9984043b342525f019788f97e745c71fd
/key
└── b02de829beeb3c01a63e6b25cbd421a98fef144f03b9a02e46eff9e2ca3f0bd7
/lock
/snapshot
└── 22a5af1bdc6e616f8a29579458c49627e01b32210d09adb288d1ecda7c5711ec
The S3 backend understands and accepts both forms, new backends are
always created with the default layout for compatibility reasons.
Pack Format
-----------
All files in the repository except Key and Pack files just contain raw
data, stored as ``IV || Ciphertext || MAC``. Pack files may contain one
or more Blobs of data.
A Pack's structure is as follows:
::
EncryptedBlob1 || ... || EncryptedBlobN || EncryptedHeader || Header_Length
At the end of the Pack file is a header, which describes the content.
The header is encrypted and authenticated. ``Header_Length`` is the
length of the encrypted header encoded as a four byte integer in
little-endian encoding. Placing the header at the end of a file allows
writing the blobs in a continuous stream as soon as they are read during
the backup phase. This reduces code complexity and avoids having to
re-write a file once the pack is complete and the content and length of
the header is known.
All the blobs (``EncryptedBlob1``, ``EncryptedBlobN`` etc.) are
authenticated and encrypted independently. This enables repository
reorganisation without having to touch the encrypted Blobs. In addition
it also allows efficient indexing, for only the header needs to be read
in order to find out which Blobs are contained in the Pack. Since the
header is authenticated, authenticity of the header can be checked
without having to read the complete Pack.
After decryption, a Pack's header consists of the following elements:
::
Type_Blob1 || Length(EncryptedBlob1) || Hash(Plaintext_Blob1) ||
[...]
Type_BlobN || Length(EncryptedBlobN) || Hash(Plaintext_Blobn) ||
This is enough to calculate the offsets for all the Blobs in the Pack.
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:
+--------+-----------+
| Type | Meaning |
+========+===========+
| 0 | data |
+--------+-----------+
| 1 | tree |
+--------+-----------+
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.
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 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
{
"supersedes": [
"ed54ae36197f4745ebc4b54d10e0f623eaaaedd03013eb7ae90df881b7781452"
],
"packs": [
{
"id": "73d04e6125cf3c28a299cc2f3cca3b78ceac396e4fcf9575e34536b26782413c",
"blobs": [
{
"id": "3ec79977ef0cf5de7b08cd12b874cd0f62bbaf7f07f3497a5b1bbcc8cb39b1ce",
"type": "data",
"offset": 0,
"length": 25
},{
"id": "9ccb846e60d90d4eb915848add7aa7ea1e4bbabfc60e573db9f7bfb2789afbae",
"type": "tree",
"offset": 38,
"length": 100
},
{
"id": "d3dc577b4ffd38cc4b32122cabf8655a0223ed22edfd93b353dc0c3f2b0fdf66",
"type": "data",
"offset": 150,
"length": 123
}
]
}, [...]
]
}
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 field ``supersedes`` lists the storage IDs of index files that have
been replaced with the current index file. This happens when index files
are repacked, for example when old snapshots are removed and Packs are
recombined.
There may be an arbitrary number of index files, containing information
on non-disjoint sets of Packs. The number of packs described in a single
file is chosen so that the file size is kept below 8 MiB.
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
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
Poly1305-AES message-authentication
code <http://cr.yp.to/mac/poly1305-20050329.pdf>`__ by Dan Bernstein.
The data is then encrypted with AES-256 and afterwards a message
authentication code (MAC) is computed over the ciphertext, everything is
then stored as IV \|\| CIPHERTEXT \|\| MAC.
The directory ``keys`` contains key files. These are simple JSON
documents which contain all data that is needed to derive the
repository's master encryption and message authentication keys from a
user's password. The JSON document from the repository can be
pretty-printed for example by using the Python module ``json``
(shortened to increase readability):
::
$ python -mjson.tool /tmp/restic-repo/keys/b02de82*
{
"hostname": "kasimir",
"username": "fd0"
"kdf": "scrypt",
"N": 65536,
"r": 8,
"p": 1,
"created": "2015-01-02T18:10:13.48307196+01:00",
"data": "tGwYeKoM0C4j4/9DFrVEmMGAldvEn/+iKC3te/QE/6ox/V4qz58FUOgMa0Bb1cIJ6asrypCx/Ti/pRXCPHLDkIJbNYd2ybC+fLhFIJVLCvkMS+trdywsUkglUbTbi+7+Ldsul5jpAj9vTZ25ajDc+4FKtWEcCWL5ICAOoTAxnPgT+Lh8ByGQBH6KbdWabqamLzTRWxePFoYuxa7yXgmj9A==",
"salt": "uW4fEI1+IOzj7ED9mVor+yTSJFd68DGlGOeLgJELYsTU5ikhG/83/+jGd4KKAaQdSrsfzrdOhAMftTSih5Ux6w==",
}
When the repository is opened by restic, the user is prompted for the
repository password. This is then used with ``scrypt``, a key derivation
function (KDF), and the supplied parameters (``N``, ``r``, ``p`` and
``salt``) to derive 64 key bytes. The first 32 bytes are used as the
encryption key (for AES-256) and the last 32 bytes are used as the
message authentication key (for Poly1305-AES). These last 32 bytes are
divided into a 16 byte AES key ``k`` followed by 16 bytes of secret key
``r``. The key ``r`` is then masked for use with Poly1305 (see the paper
for details).
Those message authentication keys (``k`` and ``r``) are used to compute
a MAC over the bytes contained in the JSON field ``data`` (after
removing the Base64 encoding and not including the last 32 byte). If the
password is incorrect or the key file has been tampered with, the
computed MAC will not match the last 16 bytes of the data, and restic
exits with an error. Otherwise, the data is decrypted with the
encryption key derived from ``scrypt``. This yields a JSON document
which contains the master encryption and message authentication keys for
this repository (encoded in Base64). The command
``restic cat masterkey`` can be used as follows to decrypt and
pretty-print the master key:
.. code-block:: console
$ restic -r /tmp/restic-repo cat masterkey
{
"mac": {
"k": "evFWd9wWlndL9jc501268g==",
"r": "E9eEDnSJZgqwTOkDtOp+Dw=="
},
"encrypt": "UQCqa0lKZ94PygPxMRqkePTZnHRYh1k1pX2k2lM2v3Q=",
}
All data in the repository is encrypted and authenticated with these
master keys. For encryption, the AES-256 algorithm in Counter mode is
used. For message authentication, Poly1305-AES is used as described
above.
A repository can have several different passwords, with a key file for
each. This way, the password can be changed without having to re-encrypt
all data.
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 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.
The command ``restic cat snapshot`` can be used as follows to decrypt
and pretty-print the contents of a snapshot file:
.. code-block:: console
$ restic -r /tmp/restic-repo cat snapshot 251c2e58
enter password for repository:
{
"time": "2015-01-02T18:10:50.895208559+01:00",
"tree": "2da81727b6585232894cfbb8f8bdab8d1eccd3d8f7c92bc934d62e62e618ffdf",
"dir": "/tmp/testdata",
"hostname": "kasimir",
"username": "fd0",
"uid": 1000,
"gid": 100,
"tags": [
"NL"
]
}
Here it can be seen that this snapshot represents the contents of the
directory ``/tmp/testdata``. The most important field is ``tree``. When
the meta data (e.g. the tags) of a snapshot change, the snapshot needs
to be re-encrypted and saved. This will change the storage ID, so in
order to relate these seemingly different snapshots, a field
``original`` is introduced which contains the ID of the original
snapshot, e.g. after adding the tag ``DE`` to the snapshot above it
becomes:
.. code-block:: console
$ restic -r /tmp/restic-repo cat snapshot 22a5af1b
enter password for repository:
{
"time": "2015-01-02T18:10:50.895208559+01:00",
"tree": "2da81727b6585232894cfbb8f8bdab8d1eccd3d8f7c92bc934d62e62e618ffdf",
"dir": "/tmp/testdata",
"hostname": "kasimir",
"username": "fd0",
"uid": 1000,
"gid": 100,
"tags": [
"NL",
"DE"
],
"original": "251c2e5841355f743f9d4ffd3260bee765acee40a6229857e32b60446991b837"
}
Once introduced, the ``original`` field is not modified when the
snapshot's meta data is changed again.
All content within a restic repository is referenced according to its
SHA-256 hash. Before saving, each file is split into variable sized
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
header of all data Blobs can be read.
Trees and Data
--------------
A snapshot references a tree by the SHA-256 hash of the JSON string
representation of its contents. Trees and data are saved in pack files
in a subdirectory of the directory ``data``.
The command ``restic cat blob`` can be used to inspect the tree
referenced above (piping the output of the command to ``jq .`` so that
the JSON is indented):
.. code-block:: console
$ restic -r /tmp/restic-repo cat blob 2da81727b6585232894cfbb8f8bdab8d1eccd3d8f7c92bc934d62e62e618ffdf | jq .
enter password for repository:
{
"nodes": [
{
"name": "testdata",
"type": "dir",
"mode": 493,
"mtime": "2014-12-22T14:47:59.912418701+01:00",
"atime": "2014-12-06T17:49:21.748468803+01:00",
"ctime": "2014-12-22T14:47:59.912418701+01:00",
"uid": 1000,
"gid": 100,
"user": "fd0",
"inode": 409704562,
"content": null,
"subtree": "b26e315b0988ddcd1cee64c351d13a100fedbc9fdbb144a67d1b765ab280b4dc"
}
]
}
A tree contains a list of entries (in the field ``nodes``) which contain
meta data like a name and timestamps. When the entry references a
directory, the field ``subtree`` contains the plain text ID of another
tree object.
When the command ``restic cat blob`` is used, the plaintext ID is needed
to print a tree. The tree referenced above can be dumped as follows:
.. code-block:: console
$ restic -r /tmp/restic-repo cat blob b26e315b0988ddcd1cee64c351d13a100fedbc9fdbb144a67d1b765ab280b4dc
enter password for repository:
{
"nodes": [
{
"name": "testfile",
"type": "file",
"mode": 420,
"mtime": "2014-12-06T17:50:23.34513538+01:00",
"atime": "2014-12-06T17:50:23.338468713+01:00",
"ctime": "2014-12-06T17:50:23.34513538+01:00",
"uid": 1000,
"gid": 100,
"user": "fd0",
"inode": 416863351,
"size": 1234,
"links": 1,
"content": [
"50f77b3b4291e8411a027b9f9b9e64658181cc676ce6ba9958b95f268cb1109d"
]
},
[...]
]
}
This tree contains a file entry. This time, the ``subtree`` field is not
present and the ``content`` field contains a list with one plain text
SHA-256 hash.
The command ``restic cat blob`` can also be used to extract and decrypt
data given a plaintext ID, e.g. for the data mentioned above:
.. code-block:: console
$ restic -r /tmp/restic-repo cat blob 50f77b3b4291e8411a027b9f9b9e64658181cc676ce6ba9958b95f268cb1109d | sha256sum
enter password for repository:
50f77b3b4291e8411a027b9f9b9e64658181cc676ce6ba9958b95f268cb1109d -
As can be seen from the output of the program ``sha256sum``, the hash
matches the plaintext hash from the map included in the tree above, so
the correct data has been returned.
Locks
-----
The restic repository structure is designed in a way that allows
parallel access of multiple instance of restic and even parallel writes.
However, there are some functions that work more efficient or even
require exclusive access of the repository. In order to implement these
functions, restic processes are required to create a lock on the
repository before doing anything.
Locks come in two types: Exclusive and non-exclusive locks. At most one
process can have an exclusive lock on the repository, and during that
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 encrypted and authenticated the same way as
other files in the repository and contains the following JSON structure:
.. code:: json
{
"time": "2015-06-27T12:18:51.759239612+02:00",
"exclusive": false,
"hostname": "kasimir",
"username": "fd0",
"pid": 13607,
"uid": 1000,
"gid": 100
}
The field ``exclusive`` defines the type of lock. When a new lock is to
be created, restic checks all locks in the repository. When a lock is
found, it is tested if the lock is stale, which is the case for locks
with timestamps older than 30 minutes. If the lock was created on the
same machine, even for younger locks it is tested whether the process is
still alive by sending a signal to it. If that fails, restic assumes
that the process is dead and considers the lock to be stale.
When a new lock is to be created and no other conflicting locks are
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.
Backups and Deduplication
-------------------------
For creating a backup, restic scans the source directory for all files,
sub-directories and other entries. The data from each file is split into
variable length Blobs cut at offsets defined by a sliding window of 64
byte. The implementation uses Rabin Fingerprints for implementing this
Content Defined Chunking (CDC). An irreducible polynomial is selected at
random and saved in the file ``config`` when a repository is
initialized, so that watermark attacks are much harder.
Files smaller than 512 KiB are not split, Blobs are of 512 KiB to 8 MiB
in size. The implementation aims for 1 MiB Blob size on average.
For modified files, only modified Blobs have to be saved in a subsequent
backup. This even works if bytes are inserted or removed at arbitrary
positions within the file.
Threat Model
------------
The design goals for restic include being able to securely store backups
in a location that is not completely trusted, e.g. a shared system where
others can potentially access the files or (in the case of the system
administrator) even modify or delete them.
General assumptions:
- The host system a backup is created on is trusted. This is the most
basic requirement, and essential for creating trustworthy backups.
The restic backup program guarantees the following:
- Accessing the unencrypted content of stored files and metadata should
not be possible without a password for the repository. Everything
except the metadata included for informational purposes in the key
files is encrypted and authenticated.
- Modifications (intentional or unintentional) can be detected
automatically on several layers:
1. For all accesses of data stored in the repository it is checked
whether the cryptographic hash of the contents matches the storage
ID (the file's name). This way, modifications (bad RAM, broken
harddisk) can be detected easily.
2. Before decrypting any data, the MAC on the encrypted data is
checked. If there has been a modification, the MAC check will
fail. This step happens even before the data is decrypted, so data
that has been tampered with is not decrypted at all.
However, the restic backup program is not designed to protect against
attackers deleting files at the storage location. There is nothing that
can be done about this. If this needs to be guaranteed, get a secure
location without any access from third parties. If you assume that
attackers have write access to your files at the storage location,
attackers are able to figure out (e.g. based on the timestamps of the
stored files) which files belong to what snapshot. When only these files
are deleted, the particular snapshot vanished and all snapshots
depending on data that has been added in the snapshot cannot be restored
completely. Restic is not designed to detect this attack.

View File

@@ -1,69 +0,0 @@
Development
===========
Contribute
----------
Contributions are welcome! Please **open an issue first** (or add a
comment to an existing issue) if you plan to work on any code or add a
new feature. This way, duplicate work is prevented and we can discuss
your ideas and design first.
More information and a description of the development environment can be
found in `CONTRIBUTING.md <https://github.com/restic/restic/blob/master/CONTRIBUTING.md>`__.
A document describing the design of restic and the data structures stored on the
back end is contained in `Design <https://restic.readthedocs.io/en/latest/design.html>`__.
If you'd like to start contributing to restic, but don't know exactly
what do to, have a look at this great article by Dave Cheney:
`Suggestions for contributing to an Open Source
project <http://dave.cheney.net/2016/03/12/suggestions-for-contributing-to-an-open-source-project>`__
A few issues have been tagged with the label ``help wanted``, you can
start looking at those:
https://github.com/restic/restic/labels/help%20wanted
Security
--------
**Important**: If you discover something that you believe to be a
possible critical security problem, please do *not* open a GitHub issue
but send an email directly to alexander@bumpern.de. If possible, please
encrypt your email using the following PGP key
(`0x91A6868BD3F7A907 <https://pgp.mit.edu/pks/lookup?op=get&search=0xCF8F18F2844575973F79D4E191A6868BD3F7A907>`__):
::
pub 4096R/91A6868BD3F7A907 2014-11-01
Key fingerprint = CF8F 18F2 8445 7597 3F79 D4E1 91A6 868B D3F7 A907
uid Alexander Neumann <alexander@bumpern.de>
sub 4096R/D5FC2ACF4043FDF1 2014-11-01
Compatibility
-------------
Backward compatibility for backups is important so that our users are
always able to restore saved data. Therefore restic follows `Semantic
Versioning <http://semver.org>`__ to clearly define which versions are
compatible. The repository and data structures contained therein are
considered the "Public API" in the sense of Semantic Versioning. This
goes for all released versions of restic, this may not be the case for
the master branch.
We guarantee backward compatibility of all repositories within one major
version; as long as we do not increment the major version, data can be
read and restored. We strive to be fully backward compatible to all
prior versions.
Building documentation
----------------------
The restic documentation is built with `Sphinx <http://sphinx-doc.org>`__,
therefore building it locally requires a recent Python version and requirements listed in ``doc/requirements.txt``.
This example will guide you through the process using `virtualenv <https://virtualenv.pypa.io>`__:
::
$ virtualenv venv # create virtual python environment
$ source venv/bin/activate # activate the virtual environment
$ cd doc
$ pip install -r requirements.txt # install dependencies
$ make html # build html documentation
$ # open _build/html/index.html with your favorite browser

View File

@@ -26,3 +26,92 @@ The message means that there is more data stored in the repo than
strictly necessary. With high probability this is duplicate data. In
order to clean it up, the command ``restic prune`` can be used. The
cause of this bug is not yet known.
How can I specify encryption passwords automatically?
-----------------------------------------------------
When you run ``restic backup``, you need to enter the passphrase on
the console. This is not very convenient for automated backups, so you
can also provide the password through the ``--password-file`` option, or one of
the environment variables ``RESTIC_PASSWORD`` or ``RESTIC_PASSWORD_FILE``.
A discussion is in progress over implementing unattended backups happens in
:issue:`533`.
.. important:: Be careful how you set the environment; using the env
command, a `system()` call or using inline shell
scripts (e.g. `RESTIC_PASSWORD=password restic ...`)
might expose the credentials in the process list
directly and they will be readable to all users on a
system. Using export in a shell script file should be
safe, however, as the environment of a process is
`accessible only to that user`_. Please make sure that
the permissions on the files where the password is
eventually stored are safe (e.g. `0600` and owned by
root).
.. _accessible only to that user: https://security.stackexchange.com/questions/14000/environment-variable-accessibility-in-linux/14009#14009
How to prioritize restic's IO and CPU time
------------------------------------------
If you'd like to change the **IO priority** of restic, run it in the following way
::
$ ionice -c2 -n0 ./restic -r /media/your/backup/ backup /home
This runs ``restic`` in the so-called best *effort class* (``-c2``),
with the highest possible priority (``-n0``).
Take a look at the `ionice manpage`_ to learn about the other classes.
.. _ionice manpage: https://linux.die.net/man/1/ionice
To change the **CPU scheduling priority** to a higher-than-standard
value, use would run:
::
$ nice --10 ./restic -r /media/your/backup/ backup /home
Again, the `nice manpage`_ has more information.
.. _nice manpage: https://linux.die.net/man/1/nice
You can also **combine IO and CPU scheduling priority**:
::
$ ionice -c2 nice -n19 ./restic -r /media/gour/backup/ backup /home
This example puts restic in the IO class 2 (best effort) and tells the CPU
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 repo on a Synology NAS via sftp fails
--------------------------------------------------
Sometimes creating a new restic repository on a Synology NAS via sftp fails
with an error similar to the following:
::
$ restic init -r sftp:user@nas:/volume1/restic-repo init
create backend at sftp:user@nas:/volume1/restic-repo/ failed:
mkdirAll(/volume1/restic-repo/index): unable to create directories: [...]
Although you can log into the NAS via SSH and see that the directory structure
is there.
The reason for this behavior is that apparently Synology NAS expose a different
directory structure via sftp, so the path that needs to be specified is
different than the directory structure on the device and maybe even as exposed
via other protocols.
The following may work:
::
$ restic init -r sftp:user@nas:/restic-repo init

View File

@@ -4,12 +4,16 @@ Restic Documentation
.. toctree::
:maxdepth: 2
installation
manual
010_introduction
020_installation
030_preparing_a_new_repo
040_backup
045_working_with_repos
050_restore
060_forget
070_encryption
080_examples
090_participating
100_references
faq
tutorials
development
references
talks
.. include:: ../README.rst
manual_rest

View File

@@ -1,68 +0,0 @@
Installation
============
Packages
--------
Mac OS X
~~~~~~~~~
If you are using Mac OS X, you can install restic using the
`homebrew <http://brew.sh/>`__ packet manager:
.. code-block:: console
$ brew tap restic/restic
$ brew install restic
archlinux
~~~~~~~~~
On archlinux, there is a package called ``restic-git`` which can be
installed from AUR, e.g. with ``pacaur``:
.. code-block:: console
$ pacaur -S restic-git
Pre-compiled Binary
-------------------
You can download the latest pre-compiled binary from the `restic release
page <https://github.com/restic/restic/releases/latest>`__.
From Source
-----------
restic is written in the Go programming language and you need at least
Go version 1.7. 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.
In order to build restic from source, execute the following steps:
.. code-block:: console
$ git clone https://github.com/restic/restic
[...]
$ cd restic
$ go run build.go
You can easily cross-compile restic for all supported platforms, just
supply the target OS and platform via the command-line options like this
(for Windows and FreeBSD respectively):
::
$ go run build.go --goos windows --goarch amd64
$ go run build.go --goos freebsd --goarch 386
The resulting binary is statically linked and does not require any
libraries.
At the moment, the only tested compiler for restic is the official Go
compiler. Building restic with gccgo may work, but is not supported.

128
doc/man/restic-backup.1 Normal file
View File

@@ -0,0 +1,128 @@
.TH "restic backup" "1" "Jan 2017" "generated by `restic generate`" ""
.nh
.ad l
.SH NAME
.PP
restic\-backup \- Create a new backup of files and/or directories
.SH SYNOPSIS
.PP
\fBrestic backup [flags] FILE/DIR [FILE/DIR] ...\fP
.SH DESCRIPTION
.PP
The "backup" command creates a new snapshot and saves the files and directories
given as the arguments.
.SH OPTIONS
.PP
\fB\-e\fP, \fB\-\-exclude\fP=[]
exclude a \fB\fCpattern\fR (can be specified multiple times)
.PP
\fB\-\-exclude\-caches\fP[=false]
excludes cache directories that are marked with a CACHEDIR.TAG file
.PP
\fB\-\-exclude\-file\fP=[]
read exclude patterns from a \fB\fCfile\fR (can be specified multiple times)
.PP
\fB\-\-exclude\-if\-present\fP=[]
takes filename[:header], exclude contents of directories containing filename (except filename itself) if header of that file is as provided (can be specified multiple times)
.PP
\fB\-\-files\-from\fP=""
read the files to backup from file (can be combined with file args)
.PP
\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]
help for backup
.PP
\fB\-\-hostname\fP=""
set the \fB\fChostname\fR for the snapshot manually. To prevent an expensive rescan use the "parent" flag
.PP
\fB\-x\fP, \fB\-\-one\-file\-system\fP[=false]
exclude other file systems
.PP
\fB\-\-parent\fP=""
use this parent snapshot (default: last snapshot in the repo that has the same target files/directories)
.PP
\fB\-\-stdin\fP[=false]
read backup from stdin
.PP
\fB\-\-stdin\-filename\fP="stdin"
file name to use when reading from stdin
.PP
\fB\-\-tag\fP=[]
add a \fB\fCtag\fR for the new snapshot (can be specified multiple times)
.PP
\fB\-\-time\fP=""
time of the backup (ex. '2012\-11\-01 22:08:41') (default: now)
.SH OPTIONS INHERITED FROM PARENT COMMANDS
.PP
\fB\-\-cacert\fP=[]
path to load root certificates from (default: use system certificates)
.PP
\fB\-\-cache\-dir\fP=""
set the cache directory
.PP
\fB\-\-json\fP[=false]
set output mode to JSON for commands that support it
.PP
\fB\-\-limit\-download\fP=0
limits downloads to a maximum rate in KiB/s. (default: unlimited)
.PP
\fB\-\-limit\-upload\fP=0
limits uploads to a maximum rate in KiB/s. (default: unlimited)
.PP
\fB\-\-no\-cache\fP[=false]
do not use a local cache
.PP
\fB\-\-no\-lock\fP[=false]
do not lock the repo, this allows some operations on read\-only repos
.PP
\fB\-o\fP, \fB\-\-option\fP=[]
set extended option (\fB\fCkey=value\fR, can be specified multiple times)
.PP
\fB\-p\fP, \fB\-\-password\-file\fP=""
read the repository password from a file (default: $RESTIC\_PASSWORD\_FILE)
.PP
\fB\-q\fP, \fB\-\-quiet\fP[=false]
do not output comprehensive progress report
.PP
\fB\-r\fP, \fB\-\-repo\fP=""
repository to backup to or restore from (default: $RESTIC\_REPOSITORY)
.SH SEE ALSO
.PP
\fBrestic(1)\fP

75
doc/man/restic-cat.1 Normal file
View File

@@ -0,0 +1,75 @@
.TH "restic backup" "1" "Jan 2017" "generated by `restic generate`" ""
.nh
.ad l
.SH NAME
.PP
restic\-cat \- Print internal objects to stdout
.SH SYNOPSIS
.PP
\fBrestic cat [flags] [pack|blob|snapshot|index|key|masterkey|config|lock] ID\fP
.SH DESCRIPTION
.PP
The "cat" command is used to print internal objects to stdout.
.SH OPTIONS
.PP
\fB\-h\fP, \fB\-\-help\fP[=false]
help for cat
.SH OPTIONS INHERITED FROM PARENT COMMANDS
.PP
\fB\-\-cacert\fP=[]
path to load root certificates from (default: use system certificates)
.PP
\fB\-\-cache\-dir\fP=""
set the cache directory
.PP
\fB\-\-json\fP[=false]
set output mode to JSON for commands that support it
.PP
\fB\-\-limit\-download\fP=0
limits downloads to a maximum rate in KiB/s. (default: unlimited)
.PP
\fB\-\-limit\-upload\fP=0
limits uploads to a maximum rate in KiB/s. (default: unlimited)
.PP
\fB\-\-no\-cache\fP[=false]
do not use a local cache
.PP
\fB\-\-no\-lock\fP[=false]
do not lock the repo, this allows some operations on read\-only repos
.PP
\fB\-o\fP, \fB\-\-option\fP=[]
set extended option (\fB\fCkey=value\fR, can be specified multiple times)
.PP
\fB\-p\fP, \fB\-\-password\-file\fP=""
read the repository password from a file (default: $RESTIC\_PASSWORD\_FILE)
.PP
\fB\-q\fP, \fB\-\-quiet\fP[=false]
do not output comprehensive progress report
.PP
\fB\-r\fP, \fB\-\-repo\fP=""
repository to backup to or restore from (default: $RESTIC\_REPOSITORY)
.SH SEE ALSO
.PP
\fBrestic(1)\fP

92
doc/man/restic-check.1 Normal file
View File

@@ -0,0 +1,92 @@
.TH "restic backup" "1" "Jan 2017" "generated by `restic generate`" ""
.nh
.ad l
.SH NAME
.PP
restic\-check \- Check the repository for errors
.SH SYNOPSIS
.PP
\fBrestic check [flags]\fP
.SH DESCRIPTION
.PP
The "check" command tests the repository for errors and reports any errors it
finds. It can also be used to read all data and therefore simulate a restore.
.PP
By default, the "check" command will always load all data directly from the
repository and not use a local cache.
.SH OPTIONS
.PP
\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]
read all data blobs
.PP
\fB\-\-with\-cache\fP[=false]
use the cache
.SH OPTIONS INHERITED FROM PARENT COMMANDS
.PP
\fB\-\-cacert\fP=[]
path to load root certificates from (default: use system certificates)
.PP
\fB\-\-cache\-dir\fP=""
set the cache directory
.PP
\fB\-\-json\fP[=false]
set output mode to JSON for commands that support it
.PP
\fB\-\-limit\-download\fP=0
limits downloads to a maximum rate in KiB/s. (default: unlimited)
.PP
\fB\-\-limit\-upload\fP=0
limits uploads to a maximum rate in KiB/s. (default: unlimited)
.PP
\fB\-\-no\-cache\fP[=false]
do not use a local cache
.PP
\fB\-\-no\-lock\fP[=false]
do not lock the repo, this allows some operations on read\-only repos
.PP
\fB\-o\fP, \fB\-\-option\fP=[]
set extended option (\fB\fCkey=value\fR, can be specified multiple times)
.PP
\fB\-p\fP, \fB\-\-password\-file\fP=""
read the repository password from a file (default: $RESTIC\_PASSWORD\_FILE)
.PP
\fB\-q\fP, \fB\-\-quiet\fP[=false]
do not output comprehensive progress report
.PP
\fB\-r\fP, \fB\-\-repo\fP=""
repository to backup to or restore from (default: $RESTIC\_REPOSITORY)
.SH SEE ALSO
.PP
\fBrestic(1)\fP

92
doc/man/restic-dump.1 Normal file
View File

@@ -0,0 +1,92 @@
.TH "restic backup" "1" "Jan 2017" "generated by `restic generate`" ""
.nh
.ad l
.SH NAME
.PP
restic\-dump \- Print a backed\-up file to stdout
.SH SYNOPSIS
.PP
\fBrestic dump [flags] snapshotID file\fP
.SH DESCRIPTION
.PP
The "dump" command extracts a single file from a snapshot from the repository and
prints its contents to stdout.
.PP
The special snapshot "latest" can be used to use the latest snapshot in the
repository.
.SH OPTIONS
.PP
\fB\-h\fP, \fB\-\-help\fP[=false]
help for dump
.PP
\fB\-H\fP, \fB\-\-host\fP=""
only consider snapshots for this host when the snapshot ID is "latest"
.PP
\fB\-\-path\fP=[]
only consider snapshots which include this (absolute) \fB\fCpath\fR for snapshot ID "latest"
.PP
\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=[]
path to load root certificates from (default: use system certificates)
.PP
\fB\-\-cache\-dir\fP=""
set the cache directory
.PP
\fB\-\-json\fP[=false]
set output mode to JSON for commands that support it
.PP
\fB\-\-limit\-download\fP=0
limits downloads to a maximum rate in KiB/s. (default: unlimited)
.PP
\fB\-\-limit\-upload\fP=0
limits uploads to a maximum rate in KiB/s. (default: unlimited)
.PP
\fB\-\-no\-cache\fP[=false]
do not use a local cache
.PP
\fB\-\-no\-lock\fP[=false]
do not lock the repo, this allows some operations on read\-only repos
.PP
\fB\-o\fP, \fB\-\-option\fP=[]
set extended option (\fB\fCkey=value\fR, can be specified multiple times)
.PP
\fB\-p\fP, \fB\-\-password\-file\fP=""
read the repository password from a file (default: $RESTIC\_PASSWORD\_FILE)
.PP
\fB\-q\fP, \fB\-\-quiet\fP[=false]
do not output comprehensive progress report
.PP
\fB\-r\fP, \fB\-\-repo\fP=""
repository to backup to or restore from (default: $RESTIC\_REPOSITORY)
.SH SEE ALSO
.PP
\fBrestic(1)\fP

108
doc/man/restic-find.1 Normal file
View File

@@ -0,0 +1,108 @@
.TH "restic backup" "1" "Jan 2017" "generated by `restic generate`" ""
.nh
.ad l
.SH NAME
.PP
restic\-find \- Find a file or directory
.SH SYNOPSIS
.PP
\fBrestic find [flags] PATTERN\fP
.SH DESCRIPTION
.PP
The "find" command searches for files or directories in snapshots stored in the
repo.
.SH OPTIONS
.PP
\fB\-h\fP, \fB\-\-help\fP[=false]
help for find
.PP
\fB\-H\fP, \fB\-\-host\fP=""
only consider snapshots for this \fB\fChost\fR, when no snapshot ID is given
.PP
\fB\-i\fP, \fB\-\-ignore\-case\fP[=false]
ignore case for pattern
.PP
\fB\-l\fP, \fB\-\-long\fP[=false]
use a long listing format showing size and mode
.PP
\fB\-N\fP, \fB\-\-newest\fP=""
newest modification date/time
.PP
\fB\-O\fP, \fB\-\-oldest\fP=""
oldest modification date/time
.PP
\fB\-\-path\fP=[]
only consider snapshots which include this (absolute) \fB\fCpath\fR, when no snapshot\-ID is given
.PP
\fB\-s\fP, \fB\-\-snapshot\fP=[]
snapshot \fB\fCid\fR to search in (can be given multiple times)
.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=[]
path to load root certificates from (default: use system certificates)
.PP
\fB\-\-cache\-dir\fP=""
set the cache directory
.PP
\fB\-\-json\fP[=false]
set output mode to JSON for commands that support it
.PP
\fB\-\-limit\-download\fP=0
limits downloads to a maximum rate in KiB/s. (default: unlimited)
.PP
\fB\-\-limit\-upload\fP=0
limits uploads to a maximum rate in KiB/s. (default: unlimited)
.PP
\fB\-\-no\-cache\fP[=false]
do not use a local cache
.PP
\fB\-\-no\-lock\fP[=false]
do not lock the repo, this allows some operations on read\-only repos
.PP
\fB\-o\fP, \fB\-\-option\fP=[]
set extended option (\fB\fCkey=value\fR, can be specified multiple times)
.PP
\fB\-p\fP, \fB\-\-password\-file\fP=""
read the repository password from a file (default: $RESTIC\_PASSWORD\_FILE)
.PP
\fB\-q\fP, \fB\-\-quiet\fP[=false]
do not output comprehensive progress report
.PP
\fB\-r\fP, \fB\-\-repo\fP=""
repository to backup to or restore from (default: $RESTIC\_REPOSITORY)
.SH SEE ALSO
.PP
\fBrestic(1)\fP

138
doc/man/restic-forget.1 Normal file
View File

@@ -0,0 +1,138 @@
.TH "restic backup" "1" "Jan 2017" "generated by `restic generate`" ""
.nh
.ad l
.SH NAME
.PP
restic\-forget \- Remove snapshots from the repository
.SH SYNOPSIS
.PP
\fBrestic forget [flags] [snapshot ID] [...]\fP
.SH DESCRIPTION
.PP
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 this (now unreferenced)
data after 'forget' was run successfully, see the 'prune' command.
.SH OPTIONS
.PP
\fB\-l\fP, \fB\-\-keep\-last\fP=0
keep the last \fB\fCn\fR snapshots
.PP
\fB\-H\fP, \fB\-\-keep\-hourly\fP=0
keep the last \fB\fCn\fR hourly snapshots
.PP
\fB\-d\fP, \fB\-\-keep\-daily\fP=0
keep the last \fB\fCn\fR daily snapshots
.PP
\fB\-w\fP, \fB\-\-keep\-weekly\fP=0
keep the last \fB\fCn\fR weekly snapshots
.PP
\fB\-m\fP, \fB\-\-keep\-monthly\fP=0
keep the last \fB\fCn\fR monthly snapshots
.PP
\fB\-y\fP, \fB\-\-keep\-yearly\fP=0
keep the last \fB\fCn\fR yearly snapshots
.PP
\fB\-\-keep\-tag\fP=[]
keep snapshots with this \fB\fCtaglist\fR (can be specified multiple times)
.PP
\fB\-\-host\fP=""
only consider snapshots with the given \fB\fChost\fR
.PP
\fB\-\-hostname\fP=""
only consider snapshots with the given \fB\fChostname\fR (deprecated)
.PP
\fB\-\-tag\fP=[]
only consider snapshots which include this \fB\fCtaglist\fR in the format \fB\fCtag[,tag,...]\fR (can be specified multiple times)
.PP
\fB\-\-path\fP=[]
only consider snapshots which include this (absolute) \fB\fCpath\fR (can be specified multiple times)
.PP
\fB\-c\fP, \fB\-\-compact\fP[=false]
use compact format
.PP
\fB\-g\fP, \fB\-\-group\-by\fP="host,paths"
string for grouping snapshots by host,paths,tags
.PP
\fB\-n\fP, \fB\-\-dry\-run\fP[=false]
do not delete anything, just print what would be done
.PP
\fB\-\-prune\fP[=false]
automatically run the 'prune' command if snapshots have been removed
.PP
\fB\-h\fP, \fB\-\-help\fP[=false]
help for forget
.SH OPTIONS INHERITED FROM PARENT COMMANDS
.PP
\fB\-\-cacert\fP=[]
path to load root certificates from (default: use system certificates)
.PP
\fB\-\-cache\-dir\fP=""
set the cache directory
.PP
\fB\-\-json\fP[=false]
set output mode to JSON for commands that support it
.PP
\fB\-\-limit\-download\fP=0
limits downloads to a maximum rate in KiB/s. (default: unlimited)
.PP
\fB\-\-limit\-upload\fP=0
limits uploads to a maximum rate in KiB/s. (default: unlimited)
.PP
\fB\-\-no\-cache\fP[=false]
do not use a local cache
.PP
\fB\-\-no\-lock\fP[=false]
do not lock the repo, this allows some operations on read\-only repos
.PP
\fB\-o\fP, \fB\-\-option\fP=[]
set extended option (\fB\fCkey=value\fR, can be specified multiple times)
.PP
\fB\-p\fP, \fB\-\-password\-file\fP=""
read the repository password from a file (default: $RESTIC\_PASSWORD\_FILE)
.PP
\fB\-q\fP, \fB\-\-quiet\fP[=false]
do not output comprehensive progress report
.PP
\fB\-r\fP, \fB\-\-repo\fP=""
repository to backup to or restore from (default: $RESTIC\_REPOSITORY)
.SH SEE ALSO
.PP
\fBrestic(1)\fP

88
doc/man/restic-generate.1 Normal file
View File

@@ -0,0 +1,88 @@
.TH "restic backup" "1" "Jan 2017" "generated by `restic generate`" ""
.nh
.ad l
.SH NAME
.PP
restic\-generate \- Generate manual pages and auto\-completion files (bash, zsh)
.SH SYNOPSIS
.PP
\fBrestic generate [command] [flags]\fP
.SH DESCRIPTION
.PP
The "generate" command writes automatically generated files like the man pages
and the auto\-completion files for bash and zsh).
.SH OPTIONS
.PP
\fB\-\-bash\-completion\fP=""
write bash completion \fB\fCfile\fR
.PP
\fB\-h\fP, \fB\-\-help\fP[=false]
help for generate
.PP
\fB\-\-man\fP=""
write man pages to \fB\fCdirectory\fR
.PP
\fB\-\-zsh\-completion\fP=""
write zsh completion \fB\fCfile\fR
.SH OPTIONS INHERITED FROM PARENT COMMANDS
.PP
\fB\-\-cacert\fP=[]
path to load root certificates from (default: use system certificates)
.PP
\fB\-\-cache\-dir\fP=""
set the cache directory
.PP
\fB\-\-json\fP[=false]
set output mode to JSON for commands that support it
.PP
\fB\-\-limit\-download\fP=0
limits downloads to a maximum rate in KiB/s. (default: unlimited)
.PP
\fB\-\-limit\-upload\fP=0
limits uploads to a maximum rate in KiB/s. (default: unlimited)
.PP
\fB\-\-no\-cache\fP[=false]
do not use a local cache
.PP
\fB\-\-no\-lock\fP[=false]
do not lock the repo, this allows some operations on read\-only repos
.PP
\fB\-o\fP, \fB\-\-option\fP=[]
set extended option (\fB\fCkey=value\fR, can be specified multiple times)
.PP
\fB\-p\fP, \fB\-\-password\-file\fP=""
read the repository password from a file (default: $RESTIC\_PASSWORD\_FILE)
.PP
\fB\-q\fP, \fB\-\-quiet\fP[=false]
do not output comprehensive progress report
.PP
\fB\-r\fP, \fB\-\-repo\fP=""
repository to backup to or restore from (default: $RESTIC\_REPOSITORY)
.SH SEE ALSO
.PP
\fBrestic(1)\fP

75
doc/man/restic-init.1 Normal file
View File

@@ -0,0 +1,75 @@
.TH "restic backup" "1" "Jan 2017" "generated by `restic generate`" ""
.nh
.ad l
.SH NAME
.PP
restic\-init \- Initialize a new repository
.SH SYNOPSIS
.PP
\fBrestic init [flags]\fP
.SH DESCRIPTION
.PP
The "init" command initializes a new repository.
.SH OPTIONS
.PP
\fB\-h\fP, \fB\-\-help\fP[=false]
help for init
.SH OPTIONS INHERITED FROM PARENT COMMANDS
.PP
\fB\-\-cacert\fP=[]
path to load root certificates from (default: use system certificates)
.PP
\fB\-\-cache\-dir\fP=""
set the cache directory
.PP
\fB\-\-json\fP[=false]
set output mode to JSON for commands that support it
.PP
\fB\-\-limit\-download\fP=0
limits downloads to a maximum rate in KiB/s. (default: unlimited)
.PP
\fB\-\-limit\-upload\fP=0
limits uploads to a maximum rate in KiB/s. (default: unlimited)
.PP
\fB\-\-no\-cache\fP[=false]
do not use a local cache
.PP
\fB\-\-no\-lock\fP[=false]
do not lock the repo, this allows some operations on read\-only repos
.PP
\fB\-o\fP, \fB\-\-option\fP=[]
set extended option (\fB\fCkey=value\fR, can be specified multiple times)
.PP
\fB\-p\fP, \fB\-\-password\-file\fP=""
read the repository password from a file (default: $RESTIC\_PASSWORD\_FILE)
.PP
\fB\-q\fP, \fB\-\-quiet\fP[=false]
do not output comprehensive progress report
.PP
\fB\-r\fP, \fB\-\-repo\fP=""
repository to backup to or restore from (default: $RESTIC\_REPOSITORY)
.SH SEE ALSO
.PP
\fBrestic(1)\fP

75
doc/man/restic-key.1 Normal file
View File

@@ -0,0 +1,75 @@
.TH "restic backup" "1" "Jan 2017" "generated by `restic generate`" ""
.nh
.ad l
.SH NAME
.PP
restic\-key \- Manage keys (passwords)
.SH SYNOPSIS
.PP
\fBrestic key [list|add|remove|passwd] [ID] [flags]\fP
.SH DESCRIPTION
.PP
The "key" command manages keys (passwords) for accessing the repository.
.SH OPTIONS
.PP
\fB\-h\fP, \fB\-\-help\fP[=false]
help for key
.SH OPTIONS INHERITED FROM PARENT COMMANDS
.PP
\fB\-\-cacert\fP=[]
path to load root certificates from (default: use system certificates)
.PP
\fB\-\-cache\-dir\fP=""
set the cache directory
.PP
\fB\-\-json\fP[=false]
set output mode to JSON for commands that support it
.PP
\fB\-\-limit\-download\fP=0
limits downloads to a maximum rate in KiB/s. (default: unlimited)
.PP
\fB\-\-limit\-upload\fP=0
limits uploads to a maximum rate in KiB/s. (default: unlimited)
.PP
\fB\-\-no\-cache\fP[=false]
do not use a local cache
.PP
\fB\-\-no\-lock\fP[=false]
do not lock the repo, this allows some operations on read\-only repos
.PP
\fB\-o\fP, \fB\-\-option\fP=[]
set extended option (\fB\fCkey=value\fR, can be specified multiple times)
.PP
\fB\-p\fP, \fB\-\-password\-file\fP=""
read the repository password from a file (default: $RESTIC\_PASSWORD\_FILE)
.PP
\fB\-q\fP, \fB\-\-quiet\fP[=false]
do not output comprehensive progress report
.PP
\fB\-r\fP, \fB\-\-repo\fP=""
repository to backup to or restore from (default: $RESTIC\_REPOSITORY)
.SH SEE ALSO
.PP
\fBrestic(1)\fP

75
doc/man/restic-list.1 Normal file
View File

@@ -0,0 +1,75 @@
.TH "restic backup" "1" "Jan 2017" "generated by `restic generate`" ""
.nh
.ad l
.SH NAME
.PP
restic\-list \- List objects in the repository
.SH SYNOPSIS
.PP
\fBrestic list [blobs|packs|index|snapshots|keys|locks] [flags]\fP
.SH DESCRIPTION
.PP
The "list" command allows listing objects in the repository based on type.
.SH OPTIONS
.PP
\fB\-h\fP, \fB\-\-help\fP[=false]
help for list
.SH OPTIONS INHERITED FROM PARENT COMMANDS
.PP
\fB\-\-cacert\fP=[]
path to load root certificates from (default: use system certificates)
.PP
\fB\-\-cache\-dir\fP=""
set the cache directory
.PP
\fB\-\-json\fP[=false]
set output mode to JSON for commands that support it
.PP
\fB\-\-limit\-download\fP=0
limits downloads to a maximum rate in KiB/s. (default: unlimited)
.PP
\fB\-\-limit\-upload\fP=0
limits uploads to a maximum rate in KiB/s. (default: unlimited)
.PP
\fB\-\-no\-cache\fP[=false]
do not use a local cache
.PP
\fB\-\-no\-lock\fP[=false]
do not lock the repo, this allows some operations on read\-only repos
.PP
\fB\-o\fP, \fB\-\-option\fP=[]
set extended option (\fB\fCkey=value\fR, can be specified multiple times)
.PP
\fB\-p\fP, \fB\-\-password\-file\fP=""
read the repository password from a file (default: $RESTIC\_PASSWORD\_FILE)
.PP
\fB\-q\fP, \fB\-\-quiet\fP[=false]
do not output comprehensive progress report
.PP
\fB\-r\fP, \fB\-\-repo\fP=""
repository to backup to or restore from (default: $RESTIC\_REPOSITORY)
.SH SEE ALSO
.PP
\fBrestic(1)\fP

94
doc/man/restic-ls.1 Normal file
View File

@@ -0,0 +1,94 @@
.TH "restic backup" "1" "Jan 2017" "generated by `restic generate`" ""
.nh
.ad l
.SH NAME
.PP
restic\-ls \- List files in a snapshot
.SH SYNOPSIS
.PP
\fBrestic ls [flags] [snapshot\-ID ...]\fP
.SH DESCRIPTION
.PP
The "ls" command allows listing files and directories in a snapshot.
.PP
The special snapshot\-ID "latest" can be used to list files and directories of the latest snapshot in the repository.
.SH OPTIONS
.PP
\fB\-h\fP, \fB\-\-help\fP[=false]
help for ls
.PP
\fB\-H\fP, \fB\-\-host\fP=""
only consider snapshots for this \fB\fChost\fR, when no snapshot ID is given
.PP
\fB\-l\fP, \fB\-\-long\fP[=false]
use a long listing format showing size and mode
.PP
\fB\-\-path\fP=[]
only consider snapshots which include this (absolute) \fB\fCpath\fR, when no snapshot ID is given
.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=[]
path to load root certificates from (default: use system certificates)
.PP
\fB\-\-cache\-dir\fP=""
set the cache directory
.PP
\fB\-\-json\fP[=false]
set output mode to JSON for commands that support it
.PP
\fB\-\-limit\-download\fP=0
limits downloads to a maximum rate in KiB/s. (default: unlimited)
.PP
\fB\-\-limit\-upload\fP=0
limits uploads to a maximum rate in KiB/s. (default: unlimited)
.PP
\fB\-\-no\-cache\fP[=false]
do not use a local cache
.PP
\fB\-\-no\-lock\fP[=false]
do not lock the repo, this allows some operations on read\-only repos
.PP
\fB\-o\fP, \fB\-\-option\fP=[]
set extended option (\fB\fCkey=value\fR, can be specified multiple times)
.PP
\fB\-p\fP, \fB\-\-password\-file\fP=""
read the repository password from a file (default: $RESTIC\_PASSWORD\_FILE)
.PP
\fB\-q\fP, \fB\-\-quiet\fP[=false]
do not output comprehensive progress report
.PP
\fB\-r\fP, \fB\-\-repo\fP=""
repository to backup to or restore from (default: $RESTIC\_REPOSITORY)
.SH SEE ALSO
.PP
\fBrestic(1)\fP

80
doc/man/restic-migrate.1 Normal file
View File

@@ -0,0 +1,80 @@
.TH "restic backup" "1" "Jan 2017" "generated by `restic generate`" ""
.nh
.ad l
.SH NAME
.PP
restic\-migrate \- Apply migrations
.SH SYNOPSIS
.PP
\fBrestic migrate [name] [flags]\fP
.SH DESCRIPTION
.PP
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.
.SH OPTIONS
.PP
\fB\-f\fP, \fB\-\-force\fP[=false]
apply a migration a second time
.PP
\fB\-h\fP, \fB\-\-help\fP[=false]
help for migrate
.SH OPTIONS INHERITED FROM PARENT COMMANDS
.PP
\fB\-\-cacert\fP=[]
path to load root certificates from (default: use system certificates)
.PP
\fB\-\-cache\-dir\fP=""
set the cache directory
.PP
\fB\-\-json\fP[=false]
set output mode to JSON for commands that support it
.PP
\fB\-\-limit\-download\fP=0
limits downloads to a maximum rate in KiB/s. (default: unlimited)
.PP
\fB\-\-limit\-upload\fP=0
limits uploads to a maximum rate in KiB/s. (default: unlimited)
.PP
\fB\-\-no\-cache\fP[=false]
do not use a local cache
.PP
\fB\-\-no\-lock\fP[=false]
do not lock the repo, this allows some operations on read\-only repos
.PP
\fB\-o\fP, \fB\-\-option\fP=[]
set extended option (\fB\fCkey=value\fR, can be specified multiple times)
.PP
\fB\-p\fP, \fB\-\-password\-file\fP=""
read the repository password from a file (default: $RESTIC\_PASSWORD\_FILE)
.PP
\fB\-q\fP, \fB\-\-quiet\fP[=false]
do not output comprehensive progress report
.PP
\fB\-r\fP, \fB\-\-repo\fP=""
repository to backup to or restore from (default: $RESTIC\_REPOSITORY)
.SH SEE ALSO
.PP
\fBrestic(1)\fP

100
doc/man/restic-mount.1 Normal file
View File

@@ -0,0 +1,100 @@
.TH "restic backup" "1" "Jan 2017" "generated by `restic generate`" ""
.nh
.ad l
.SH NAME
.PP
restic\-mount \- Mount the repository
.SH SYNOPSIS
.PP
\fBrestic mount [flags] mountpoint\fP
.SH DESCRIPTION
.PP
The "mount" command mounts the repository via fuse to a directory. This is a
read\-only mount.
.SH OPTIONS
.PP
\fB\-\-allow\-other\fP[=false]
allow other users to access the data in the mounted directory
.PP
\fB\-\-allow\-root\fP[=false]
allow root user to access the data in the mounted directory
.PP
\fB\-h\fP, \fB\-\-help\fP[=false]
help for mount
.PP
\fB\-H\fP, \fB\-\-host\fP=""
only consider snapshots for this host
.PP
\fB\-\-owner\-root\fP[=false]
use 'root' as the owner of files and dirs
.PP
\fB\-\-path\fP=[]
only consider snapshots which include this (absolute) \fB\fCpath\fR
.PP
\fB\-\-tag\fP=[]
only consider snapshots which include this \fB\fCtaglist\fR
.SH OPTIONS INHERITED FROM PARENT COMMANDS
.PP
\fB\-\-cacert\fP=[]
path to load root certificates from (default: use system certificates)
.PP
\fB\-\-cache\-dir\fP=""
set the cache directory
.PP
\fB\-\-json\fP[=false]
set output mode to JSON for commands that support it
.PP
\fB\-\-limit\-download\fP=0
limits downloads to a maximum rate in KiB/s. (default: unlimited)
.PP
\fB\-\-limit\-upload\fP=0
limits uploads to a maximum rate in KiB/s. (default: unlimited)
.PP
\fB\-\-no\-cache\fP[=false]
do not use a local cache
.PP
\fB\-\-no\-lock\fP[=false]
do not lock the repo, this allows some operations on read\-only repos
.PP
\fB\-o\fP, \fB\-\-option\fP=[]
set extended option (\fB\fCkey=value\fR, can be specified multiple times)
.PP
\fB\-p\fP, \fB\-\-password\-file\fP=""
read the repository password from a file (default: $RESTIC\_PASSWORD\_FILE)
.PP
\fB\-q\fP, \fB\-\-quiet\fP[=false]
do not output comprehensive progress report
.PP
\fB\-r\fP, \fB\-\-repo\fP=""
repository to backup to or restore from (default: $RESTIC\_REPOSITORY)
.SH SEE ALSO
.PP
\fBrestic(1)\fP

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