duplicity

Siehe auch

duplicity unterstützt gelöschte Dateien, volle Unix-Berechtigungen, Verzeichnisse und symbolische Links, fifos und Gerätedateien, aber keine Hardlinks. Schön ist, dass es eine ganze Reihe an Backends unterstützt (anzugeben im URL-Format), darunter neben lokalem Dateisystem, SSH/scp, WebDAV und rsync auch S3- und Swift-Buckets.

duplicity sichert Dateien und Verzeichnisse, indem es mit Hilfe von gpg verschlüsselte „Volumes“ im tar-Format erstellt und diese auf einen entfernten oder lokalen Dateiserver hochlädt. Da duplicity librsync verwendet, sind die inkrementellen Archive sehr klein und enthalten nur die Teile der Dateien, die sich seit der letzten Sicherung geändert haben.

Ohne weitere Angabe von Parametern erstellt duplicity automatisch mit gzip komprimierte .difftar-Dateien, mit einer maximalen Grösse von 200 MB. Sobald solch eine Datei vorliegt, wird sie mit GnuPG verschlüsselt und ins Backend hochgeladen. Danach wird das Backup fortgesetzt.

Bemerkung

So sieht ein gpg-Aufruf auf, den duplicity unter der Haube nutzt: gpg --passphrase-fd 14 --logger-fd 9 --batch --no-tty --recipient B854C23CFBAB3B9D --no-secmem-warning --ignore-mdc-error --pinentry-mode=loopback --encrypt

Links

Umgang mit duplicity

duplicity findet sich im EPEL-Repo, und ist dort stets sehr aktuell.

dnf -y install duplicity

Um keine Passwörter zur Verschlüsselung der Backups verwenden zu müssen, GPG-Schlüssel für das Chiffrieren der Backup-Repos erzeugen.

Dry Run:

# add parameter --dry-run

Tipp

duplicity erwartet Parameter-Angaben immer vor allen Pfadangaben.

Backup erstellen

Ohne Angabe von Parametern fragt duplicity ein Passwort ab, um die Repos zu verschlüsseln. Anschliessend erzeugt der erste Lauf ein Full-, und alle weiteren Läufe ein inkrementelles Backup:

duplicity /etc file:///backup/etc

Full-Backup mit bereits vorhandenen GPG-Schlüsseln erzeugen:

# backup /etc to /backup/etc
duplicity full --progress --encrypt-key=$GPG_KEY /etc file:///backup/etc

Mit jedem Full-Backup kommen diese Dateien hinzu:

duplicity-full.20220103T131323Z.manifest.gpg
duplicity-full.20220103T131323Z.vol1.difftar.gpg
duplicity-full-signatures.20220103T131323Z.sigtar.gpg

Inkrementelles Backup erzeugen:

duplicity incremental --encrypt-key=$GPG_KEY /etc file:///backup/etc

Mit jedem inkrementellen Backup kommen diese Dateien hinzu:

duplicity-inc.20220103T131323Z.to.20220103T141327Z.manifest.gpg
duplicity-inc.20220103T131323Z.to.20220103T141327Z.vol1.difftar.gpg
duplicity-new-signatures.20220103T131323Z.to.20220103T141327Z.sigtar.gpg

Volles Backup nach jeder Woche erzwingen:

# enforcing a full backup every month
duplicity incremental --full-if-older-than 1W /etc file:///backup/etc

Tipp

duplicity legt Cache-Dateien unter $USER/.cache/duplicity ab. Aber Achtung: Man sollte nie die gleiche $BACKUP_DEST fuer mehrere verschiedene Backups verwenden, da das Cache-Directory basierend auf dem Hash vom $BACKUP_DEST berechnet wird. Bei gleicher $BACKUP_DEST wird der gefundene Cache vom anderen Backup überschrieben (z. B. Deleting local /root/.cache/duplicity/5d4d982584...e4a79d/duplicity-new-signatures.20230328T220402Z.to.20230329T220402Z.sigtar.gz (not authoritative at backend).).

Dateien von der Sicherung ausnehmen:

duplicity --exclude **/*.git* --exclude **/log ...

Backup findet dann statt, wenn kein anderer Prozess nach I/O fragt, und dann mit hohem „Nice“-Wert:

ionice --class 3 nice --adjustment=19 duplicity ...

Vorhandene Backups auflisten:

duplicity collection-status --encrypt-key=$GPG_KEY file:///backup/etc

Im Backup enthaltene Dateien auflisten:

duplicity list-current-files --encrypt-key=$GPG_KEY file:///backup/etc

Alle Dateien im Backup mit dem im Dateisystem vergleichen (und die Namen der geänderten Dateien auflisten):

duplicity verify --encrypt-key=$GPG_KEY --verbosity=notice file:///backup/etc /etc

Restore

Kompletter Restore (Zielverzeichnis darf dabei nicht existieren):

duplicity restore --progress --encrypt-key=$GPG_KEY file:///backup/etc /etc

Restore einer einzelnen Datei /etc/ssh/sshd_config vor drei Tagen oder für ein konkretes Backup-Datum nach /tmp/test1 wiederherstellen:

duplicity restore --time 2023-04-05 --encrypt-key=$GPG_KEY --file-to-restore ssh/sshd_config file:///backup/etc /tmp/test1
duplicity restore --time 3D --encrypt-key=$GPG_KEY --file-to-restore ssh/sshd_config file:///backup/etc /tmp/test1

Kompletter Restore mit alternativen temporären Verzeichnis (welches vorher angelegt werden muss; Zielverzeichnis darf dabei nicht existieren):

duplicity restore --progress --tempdir /alternativ/folder --encrypt-key=$GPG_KEY file:///backup/etc /etc

Backups > 90 Tage löschen:

duplicity remove-older-than 90D --encrypt-key=$GPG_KEY --force file:///backup/etc

Angaben für --verbosity:

  • error

  • warning

  • notice

  • info

  • debug

Verwendung in Kombination mit LFOps

Bei den folgenden Anweisungen wird angenommen, dass duplicity mit der LFOps Ansible-Rolle der Linuxfabrik installiert und konfiguriert wurde.

Geloggt wird nach /var/log/duplicity/\*.log.

Wichtig

Solange duba noch keinen einfachen Restore-Modus bietet, muss bei der nachfolgenden direkten Arbeit mit duplicity zuerst das Environment mit folgenden Variablen gefüttert werden. Die Angaben finden sich in /etc/duba/duba.json.

# Backup behaviour
export BACKUP_DEST='swift://server.example.org'

# Swift Storage Backend
CF_BACKEND='swift'
export SWIFT_AUTHURL='https://swiss-backup02.infomaniak.com/identity/v3'
export SWIFT_USERNAME='SBI-AB123456'
export SWIFT_PASSWORD='mypassword'
export SWIFT_AUTHVERSION='3'
export SWIFT_TENANTNAME='sb_project_SBI-AB123456'

# GPG
export GPG_KEY='TheShortLocalGPGKey'
export PASSPHRASE=''

Python venv aktivieren (duplicity wird in der venv /opt/python-venv/duplicity installiert):

source /opt/python-venv/duplicity/bin/activate

Restore von /etc nach /tmp/etc auf der gleichen Maschine.

# show the available versions
duplicity collection-status --encrypt-key="$GPG_KEY" "$BACKUP_DEST/etc"

# show the available files
duplicity list-current-files --encrypt-key="$GPG_KEY" "$BACKUP_DEST/etc"

mkdir /tmp/etc

# do the restore
duplicity restore --progress --encrypt-key=$GPG_KEY "$BACKUP_DEST/etc" /tmp/etc

Restore von /etc/ssh/sshd_config auf der gleichen Machine:

# show the available versions
duplicity collection-status --encrypt-key="$GPG_KEY" "$BACKUP_DEST/etc"

# show the available files
duplicity list-current-files --encrypt-key="$GPG_KEY" "$BACKUP_DEST/etc"

# do the restore
duplicity restore --progress --encrypt-key=$GPG_KEY --file-to-restore ssh/sshd_config "$BACKUP_DEST/etc" /tmp/sshd_config

# do the restore with alternative temp folder (in case the /tmp Volume is to small)
duplicity restore --progress --encrypt-key=$GPG_KEY --tempdir /alternativ/folder --file-to-restore ssh/sshd_config "$BACKUP_DEST/etc" /tmp/sshd_config

Restore einer Datei (/root/.gnupg) nach /tmp/gnupg zu lokaler Admin-Maschine:

# import the master GPG private key (look in Bitwarden)
gpg --import /tmp/private-key.txt

# check if the key is present
gpg --list-secret-keys

# set GPG_KEY to the master key
export GPG_KEY='TheShortMasterGPGKey'

# unset PASSPHRASE so it is asked interactively (or set it to the correct passphrase)
unset PASSPHRASE

# do the restore
duplicity restore --progress --encrypt-key=$GPG_KEY --file-to-restore .gnupg "$BACKUP_DEST/etc" /tmp/gnupg

# cleanup (but make sure that no active backup/restore is running at the moment)
rm -rf ~/.cache/duplicity

Disaster-Recovery (Maschine wurde neu aufgesetzt und fährt noch kein duplicity). Im Beispiel Restore von /etc nach /tmp/etc:

# restore /root/.gnupg using the method above (Restore zu lokaler Admin-Maschine)
# check if the private key of the server and is present
gpg --list-secret-keys

# install and configure duplicity using ansible
ansible-playbook --inventory /path/to/inventory linuxfabrik.lfops.duplicity --limit vm

mkdir /tmp/etc

# do the restore
duplicity restore --progress --encrypt-key=$GPG_KEY "$BACKUP_DEST/etc" /tmp/etc

Verschlüssung und Signierung

Duplicity verwendet immmer asymmetrische Verschlüsslung. Das heisst, es werden immer Key-Pairs mit einem Public- und einem Private-Key verwendet.

  • Bei der Verschlüssung wird das Backup mit dem Public-Key verschlüsselt, und kann danach nur mit dem entsprechenden Private-Key gelesen werden (Integritätssicherung).

  • Bei der Signierung wird das Backup mit dem Private-Key signiert, und dies kann danach nur mit dem entsprechenden Public-Key gelesen werden (Vertraulichkeit).

Die Verschlüsslung und Signierung kann mit dem gleichen Key-Pair gemacht werden - oder wie im folgendem Beispiel mit getrennten Schlüsselpaaren:

  • duplicity_enc: Encryption-Key

  • duplicity_sign: Signing-Key

Sind beide Keys ausgesstellt, muss der Encryption-Key mit dem Signing-Key signiert werden:

gpg --local-user duplicity_sign --sign-key duplicity_enc

# unattended
gpg --quick-sign-key $Fingerprint_SignKey $mail-address

duplicity lässt sich dann für alle Operationen wie folgt aufrufen:

duplicity ... --encrypt-key=0415328762E6F9AE --sign-key=CBBE7A2DE48F4F3D ...

Bei einem Backup muss das Passwort für den Signing-Key, bei Zugriff auf das Backup-Repo das Passwort für den Encryption-Key übergeben werden. Full und inkrementelle Backups brauchen den Encryption-Key nicht, solange der Cache unter $USER/.cache/duplicity mit den Daten auf dem Backend konsistent ist. Falls dies nicht der Fall ist, wird der Encryption-Key (und somit dessen Passwort) benötigt.

Duplicity kann mehrere GPG-Keys zur Encryption verwenden, mit geringem Overhead (Details siehe Sektion 5.1 von RFC 2440). Die Backups können mit jedem einzelnen der Encryption-Keys entschlüsselt werden. Dies erlaubt es, für mehrere Server einen „Master“-Key zu verwenden. Der Private-Key des Master-Keys wird zur Verschlüssung nicht gebraucht, und kann daher auf der lokalen Admin-Maschine bleiben. Der Server hat weiterhin sein eigenes Key-Pair, zur Verschlüssung und zur einfacheren Wiederherstellung von Files. Und im Disaster-Fall kann man mit dem Master-Key auf jeden Fall die Backups lesen.

S3-Backend

Siehe auch S3.

Anmeldeinformationen für S3 können wie folgt übergeben werden:

  • per Umgebungsvariablen AWS_ACCESS_KEY_ID und AWS_SECRET_ACCESS_KEY

  • durch die Datei ~/.aws/credentials

  • durch die Datei ~/.aws/config

  • durch die Verwendung der Dateien ~/.boto oder /etc/boto.cfg im Stil von boto2

Das URL-Schema lautet boto3+s3://.

Swift-Backend

Siehe auch Swift.

Sicherung, im Beispiel das /etc-Verzeichnis gegen Infomaniak „Swiss Backup“:

pip3 install python-swiftclient
pip3 install python-keystoneclient
# if device was created up to 2020-10:
#export SWIFT_AUTHURL='https://swiss-backup.infomaniak.com/identity/v3'
# if device was created after 2020-10:
export SWIFT_AUTHURL='https://swiss-backup02.infomaniak.com/identity/v3'

export SWIFT_AUTHVERSION='3'
export SWIFT_PASSWORD='password'
export SWIFT_TENANTNAME='sb_project_SBI-XXXXXXXX'
export SWIFT_USERNAME='SBI-XXXXXXXX'

Backup erstellen:

duplicity full --encrypt-key=$GPG_KEY /etc swift://$(hostname)/etc

Siehe auch https://www.arsouyes.org/en/blog/2020/17_backup_OVH.

duplicity Return Codes

Bedeutung der Warning-Codes (Stand 2022-03-03):

  • 1: generic

  • 2: orphaned_sig

  • 3: unnecessary_sig

  • 4: unmatched_sig

  • 5: incomplete_backup

  • 6: orphaned_backup

  • 7: ftp_ncftp_v320

  • 8: cannot_iterate

  • 9: cannot_stat

  • 10: cannot_read

  • 11: no_sig_for_time

  • 12: cannot_process

  • 13: process_skipped

Bedeutung der Error-Codes (Stand 2022-03-03):

  • 1 generic

  • 2: command_line

  • 3: hostname_mismatch

  • 4: no_manifests

  • 5: mismatched_manifests

  • 6: unreadable_manifests

  • 7: cant_open_filelist

  • 8: bad_url

  • 9: bad_archive_dir

  • 10: bad_sign_key

  • 11: restore_dir_exists

  • 12: verify_dir_doesnt_exist

  • 13: backup_dir_doesnt_exist

  • 14: file_prefix_error

  • 15: globbing_error

  • 16: redundant_inclusion

  • 17: inc_without_sigs

  • 18: no_sigs

  • 19: restore_dir_not_found

  • 20: no_restore_files

  • 21: mismatched_hash

  • 22: unsigned_volume

  • 23: user_error

  • 24: boto_old_style

  • 25: boto_lib_too_old

  • 26: boto_calling_format

  • 27: ftp_ncftp_missing

  • 28: ftp_ncftp_too_old

  • 30: exception

  • 31: gpg_failed

  • 32: s3_bucket_not_style

  • 33: not_implemented

  • 34: get_freespace_failed

  • 35: not_enough_freespace

  • 36: get_ulimit_failed

  • 37: maxopen_too_low

  • 38: connection_failed

  • 39: restart_file_not_found

  • 40: gio_not_available

  • 42 source_dir_mismatch

  • 43: ftps_lftp_missing

  • 44: volume_wrong_size

  • 45: enryption_mismatch

  • 46: pythonoptimize_set

  • 47: dpbx_nologin

  • 48: bad_request

  • 49: s3_kms_no_id

  • 50: backend_error

  • 51: backend_permission_denied

  • 52: backend_not_found

  • 53: backend_no_space

  • 54: backend_command_error

  • 55: backend_code_error

  • 126: error code for pkexec

  • 127: error code for pkexec

  • 255: error code for gksu

duplicity Cheat Sheet

Time Formats: s, m, h, D, W, M, Y

duplicity 0.8.22:

Usage:
  duplicity [full|incremental] [options] source_dir target_url
  duplicity [restore] [options] source_url target_dir
  duplicity verify [options] source_url target_dir
  duplicity collection-status [options] target_url
  duplicity list-current-files [options] target_url
  duplicity cleanup [options] target_url
  duplicity remove-older-than time [options] target_url
  duplicity remove-all-but-n-full count [options] target_url
  duplicity remove-all-inc-of-but-n-full count [options] target_url
  duplicity replicate source_url target_url

Backends and their URL formats:
  azure://container_name
  b2://account_id[:application_key]@bucket_name/[some_dir/]
  boto3+s3://bucket_name[/prefix]
  cf+http://container_name
  dpbx:///some_dir
  file:///some_dir
  ftp://user[:password]@other.host[:port]/some_dir
  ftps://user[:password]@other.host[:port]/some_dir
  gdocs://user[:password]@other.host/some_dir
  for gdrive:// a <service-account-url> like the following is required
        <serviceaccount-name>@<serviceaccount-name>.iam.gserviceaccount.com
  gdrive://<service-account-url>/target-folder/?driveID=<SHARED DRIVE ID> (for GOOGLE Shared Drive)
  gdrive://<service-account-url>/target-folder/?myDriveFolderID=<google-myDrive-folder-id> (for GOOGLE MyDrive)
  hsi://user[:password]@other.host[:port]/some_dir
  imap://user[:password]@other.host[:port]/some_dir
  mega://user[:password]@other.host/some_dir
  megav2://user[:password]@other.host/some_dir
  mf://user[:password]@other.host/some_dir
  onedrive://some_dir
  pca://container_name
  pydrive://user@other.host/some_dir
  rclone://remote:/some_dir
  rsync://user[:password]@other.host[:port]/relative_path
  rsync://user[:password]@other.host[:port]//absolute_path
  rsync://user[:password]@other.host[:port]::/module/some_dir
  s3+http://bucket_name[/prefix]
  s3://other.host[:port]/bucket_name[/prefix]
  scp://user[:password]@other.host[:port]/some_dir
  ssh://user[:password]@other.host[:port]/some_dir
  swift://container_name
  tahoe://alias/directory
  webdav://user[:password]@other.host/some_dir
  webdavs://user[:password]@other.host/some_dir

Commands:
  cleanup <target_url>
  collection-status <target_url>
  full <source_dir> <target_url>
  incr <source_dir> <target_url>
  list-current-files <target_url>
  remove-all-but-n-full <count> <target_url>
  remove-all-inc-of-but-n-full <count> <target_url>
  remove-older-than <time> <target_url>
  replicate <source_url> <target_url>
  restore <source_url> <target_dir>
  verify <target_url> <source_dir>



Options:
  -h, --help            show this help message and exit
  --allow-source-mismatch
  --archive-dir=path
  --asynchronous-upload
  --compare-data
  --copy-links
  --dry-run
  --encrypt-key=gpg-key-id
  --encrypt-secret-keyring=path
  --encrypt-sign-key=gpg-key-id
  --exclude=shell_pattern
  --exclude-device-files
  --exclude-filelist=filename
  --exclude-if-present=filename
  --exclude-other-filesystems
  --exclude-regexp=regular_expression
  --exclude-older-than=time
  --file-prefix=FILE_PREFIX
  --file-prefix-manifest=FILE_PREFIX_MANIFEST
  --file-prefix-archive=FILE_PREFIX_ARCHIVE
  --file-prefix-signature=FILE_PREFIX_SIGNATURE
  -r path, --file-to-restore=path
  --force
  --ftp-passive
  --ftp-regular
  --full-if-older-than=time
  --gio
  --gpg-binary=path
  --gpg-options=options
  --hidden-encrypt-key=gpg-key-id
  --idr-fakeroot=path
  --ignore-errors
  --imap-full-address
  --imap-mailbox=imap_mailbox
  --include=shell_pattern
  --include-filelist=filename
  --include-regexp=regular_expression
  --log-fd=file_descriptor
  --log-file=filename
  --log-timestamp
  --max-blocksize=number
  --name=backup name
  --no-encryption
  --no-compression
  --no-print-statistics
  --null-separator
  --num-retries=number
  --numeric-owner
  --do-not-restore-ownership
  --old-filenames
  --metadata-sync-mode=METADATA_SYNC_MODE
  --par2-redundancy=number
  --par2-options=options
  --par2-volumes=number
  --progress
  --progress-rate=number
  --pydevd
  --rename=RENAME
  -t time, --restore-time=time, --time=time
  --rsync-options=options
  --s3-european-buckets
  --s3-use-rrs
  --s3-use-ia
  --s3-use-glacier
  --s3-use-glacier-ir
  --s3-use-deep-archive
  --s3-use-onezone-ia
  --s3-use-new-style
  --s3-unencrypted-connection
  --s3-multipart-chunk-size=number
  --s3-multipart-max-procs=number
  --s3-multipart-max-timeout=number
  --s3-use-multiprocessing
  --s3-use-server-side-encryption
  --s3-use-server-side-kms-encryption
  --s3-kms-key-id=S3_KMS_KEY_ID
  --s3-kms-grant=S3_KMS_GRANT
  --s3-region-name=S3_REGION_NAME
  --s3-endpoint-url=S3_ENDPOINT_URL
  --swift-storage-policy=policy
  --azure-max-single-put-size=number
  --azure-max-block-size=number
  --azure-max-connections=number
  --azure-blob-tier=Hot|Cool|Archive
  --scp-command=command
  --sftp-command=command
  --cf-backend=pyrax|cloudfiles
  --b2-hide-files
  --short-filenames
  --sign-key=gpg-key-id
  --ssh-askpass
  --ssh-options=options
  --ssl-cacert-file=pem formatted bundle of certificate authorities
  --ssl-cacert-path=path to a folder with certificate authority files
  --ssl-no-check-certificate
  --tempdir=path
  --timeout=seconds
  --time-separator=char
  --use-agent
  -v [0-9], --verbosity=[0-9]
  -V, --version
  --mf-purge
  --mp-segment-size=number
  --volsize=number
  --file-changed=path
  --no-files-changed
  --show-changes-in-set=number
  --backend-retry-delay=seconds

Troubleshooting

Warning, found signatures but no corresponding backup files

Unvollständige Backups entfernen:

duplicity cleanup <target_url>
Temp space has 443584512 available, backup needs approx 482344960.

Nicht genügend Platz im temporären Verzeichnis vorhanden (meistens /tmp). Die Werte werden in Bytes angegeben.

ERROR 30 AssertionError: Cannot set filename of remote manifest to b’duplicity-inc.20240115T221802Z.to.20240116T221802Z.manifest.gpg‘; already set to b’duplicity-inc.20240115T221802Z.to.20240116T221802Z.manifest.part‘.

Meist hilft es, den lokalen Cache zu löschen: rm -rf $HOME/.cache/duplicity

ERROR 50 get, Giving up after 5 attempts. OSError: No space left on device

Nicht genügend Platz im temporären Verzeichnis vorhanden (meistens /tmp).

Built on 2024-09-30