First commit
This commit is contained in:
commit
757eed3bb8
|
@ -0,0 +1,19 @@
|
|||
Copyright 2022 bursa-pastoris
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
this software and associated documentation files (the "Software"), to deal in
|
||||
the Software without restriction, including without limitation the rights to
|
||||
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
|
||||
of the Software, and to permit persons to whom the Software is furnished to do
|
||||
so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
|
@ -0,0 +1,42 @@
|
|||
## How-to
|
||||
|
||||
This is a script to restore data from corrupted duplicity backups without the
|
||||
`manifest` file, maintaining the original directory tree.
|
||||
|
||||
The script requires unencrypted archives. If you encrypted them, you must
|
||||
decrypt them massively with
|
||||
|
||||
gpg --multifile --decrypt path/to/encrypted/archives/duplicity-full*.difftar.gpg
|
||||
|
||||
This will decrypt all the archives in the same path. Depending on their size
|
||||
it can require some time and may require you to type your passphrase multiple
|
||||
times.
|
||||
|
||||
The decrypted archives must be extracted with `tar`:
|
||||
|
||||
tar xvf path/to/decrypted/archives/*.difftar --directory=restore/path
|
||||
|
||||
After that, in `restore/path` there will be two path trees:
|
||||
- `snapshot` contains files large at most 64KB
|
||||
- `multivol-snapshot` contains all the larger files: each of them is split in
|
||||
fragments large up to 64KB each, gathered in a directory with the same name
|
||||
of the original file
|
||||
|
||||
For example, an original file `Documents/bank-account-data.md` large 100B will
|
||||
be available right after the extraction as a single file stored in
|
||||
`restore/path/snapshot/Documents/`.
|
||||
`Download/how-to-crack-assassins-creed.pdf` large 200KB will be split in
|
||||
200/64=3.125 -> 4 files, stored in
|
||||
`restore/path/multivol-snapshot/Downloads/how-to-crack-assassins-creed.pdf/`.
|
||||
|
||||
This script takes as input the *full* paths to `snapshot`, `multivol_snapshot`
|
||||
and `restore/path` and restores all the files from the first two to the third.
|
||||
|
||||
The three input parameters must be written directly into the script editing the
|
||||
values of `FROM_SNAPSHOT`, `FROM_MULTIVOL` and `TO` variables respectively.
|
||||
`TO` path must *not* exist when the script is launched.
|
||||
|
||||
## License
|
||||
|
||||
The script is released under the [Expat license](./LICENSE) (also known as the
|
||||
potentially misleading name of "MIT license").
|
|
@ -0,0 +1,44 @@
|
|||
#!/usr/bin/python3
|
||||
|
||||
import shutil
|
||||
import os
|
||||
|
||||
# FROM_SNAPSHOT
|
||||
# Path containing monovol files
|
||||
#
|
||||
# FROM_MULTIVOL
|
||||
# Path containing multivol files
|
||||
#
|
||||
# TO
|
||||
# Path to restore the files to
|
||||
|
||||
FROM_SNAPSHOT = "/home/user/duplicity/snapshot"
|
||||
FROM_MULTIVOL = "/home/user/duplicity/multivol_snapshot"
|
||||
|
||||
TO = "/home/user/restore"
|
||||
|
||||
|
||||
def main():
|
||||
# PART 1: restore monovol (`snapshot`) tree and files
|
||||
shutil.copytree(FROM_SNAPSHOT,TO)
|
||||
|
||||
# PART 2: restore multivol (`multivol_snapshot`)
|
||||
for (path, dirs, files) in os.walk(FROM_MULTIVOL):
|
||||
if len(files) == 0:
|
||||
# If path is a directory, create the directory in the restore path
|
||||
if not os.path.exists(path.replace(FROM_MULTIVOL,TO,1)):
|
||||
os.mkdir(path.replace(FROM_MULTIVOL,TO,1))
|
||||
# The triple for the directory is generated before the triples
|
||||
# for any of its subdirectories, so parents of current path
|
||||
# will always exist
|
||||
if len(dirs) == 0:
|
||||
# If path is a file, recreate it the destination path
|
||||
os.system('find "{path_to_find}" -type f | sort -V | xargs cat - > "{path_to_cat_to}"'
|
||||
.format(path_to_find=path,
|
||||
path_to_cat_to=path.replace(FROM_MULTIVOL,TO,1)
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
Reference in New Issue