diff --git a/.env.example b/.env.example index e424b4a..cfb42fc 100644 --- a/.env.example +++ b/.env.example @@ -12,10 +12,12 @@ START_TIMEOUT=90 WAIT_INTERVAL=3 START_RETRIES=1 LOCK_FILE="/run/lock/zorruno-docker-backup.lock" +ZSTD_EXTRA_OPTS="-T0 -12" # Rclone destination becomes /beaver/. RCLONE_ENABLED=0 RCLONE_REMOTE="gdrive:dockerbackups" +RCLONE_HOST_SUBDIR=1 RCLONE_EXTRA_ARGS="" # The SMB share must already be mounted by the operating system. diff --git a/README.md b/README.md index d0fcf8f..a9a4b8c 100644 --- a/README.md +++ b/README.md @@ -68,6 +68,7 @@ Copy `.env.example` to `.env`; `.env` is ignored by Git. Important values: | `CONTAINERS_FILE` | Absolute path, or path relative to this repository | | `BACKUP_HOSTNAME` | Host folder/name embedded in archive filenames | | `BACKUP_METHODS` | Ordered method names, default `rclone smb` | +| `ZSTD_EXTRA_OPTS` | Options passed to `zstd`, default `-T0 -12` | | `KEEP_COUNT` | Local archives retained per Compose project | | `PULL_IMAGES` | Pull images before restarting (`1` or `0`) | @@ -79,7 +80,9 @@ RCLONE_REMOTE="gdrive:dockerbackups" ``` Archives are copied beneath `//`. The method uses -`rclone copy`, so it does not delete older destination files. +`rclone copy`, so it does not delete older destination files. Set +`RCLONE_HOST_SUBDIR=0` when `RCLONE_REMOTE` already names the final host-specific +destination; otherwise the host name is appended automatically. ### SMB diff --git a/lib/common.sh b/lib/common.sh index 085aca6..39ca80d 100644 --- a/lib/common.sh +++ b/lib/common.sh @@ -15,9 +15,11 @@ START_TIMEOUT=90 WAIT_INTERVAL=3 START_RETRIES=1 LOCK_FILE="/run/lock/zorruno-docker-backup.lock" +ZSTD_EXTRA_OPTS="-T0 -12" RCLONE_ENABLED=0 RCLONE_REMOTE="" +RCLONE_HOST_SUBDIR=1 RCLONE_EXTRA_ARGS="" SMB_ENABLED=0 SMB_MOUNT="" diff --git a/methods/20-rclone.sh b/methods/20-rclone.sh index e1a84f4..84d3776 100755 --- a/methods/20-rclone.sh +++ b/methods/20-rclone.sh @@ -17,8 +17,10 @@ if ! is_true "$RCLONE_ENABLED" && (( FORCE == 0 )); then log SKIP "rclone method require_command rclone [[ -d "$BACKUP_ROOT" ]] || die "Backup root not found: $BACKUP_ROOT" read -r -a extra_args <<<"$RCLONE_EXTRA_ARGS" -args=(copy "$BACKUP_ROOT" "${RCLONE_REMOTE%/}/$BACKUP_HOSTNAME" "${extra_args[@]}") +destination="${RCLONE_REMOTE%/}" +is_true "$RCLONE_HOST_SUBDIR" && destination="$destination/$BACKUP_HOSTNAME" +args=(copy "$BACKUP_ROOT" "$destination" "${extra_args[@]}") (( DRY_RUN )) && args+=(--dry-run) -log INFO "Copying backups to ${RCLONE_REMOTE%/}/$BACKUP_HOSTNAME" +log INFO "Copying backups to $destination" rclone "${args[@]}" log DONE "rclone backup method completed" diff --git a/stages/10-containers.sh b/stages/10-containers.sh index 8e254ad..59d10df 100755 --- a/stages/10-containers.sh +++ b/stages/10-containers.sh @@ -29,6 +29,7 @@ done CONTAINERS_FILE="$(resolve_path "$CONTAINERS_FILE")" require_command docker require_command tar +require_command zstd [[ -d "$SOURCE_BASE_DIR" ]] || die "Docker source directory not found: $SOURCE_BASE_DIR" [[ -r "$CONTAINERS_FILE" ]] || die "Containers file not readable: $CONTAINERS_FILE" @@ -77,6 +78,7 @@ wait_for_project() { backup_project() { local project="$1" working_dir="$2" parent base output_dir output attempt + local -a zstd_args=() parent="$(dirname "$working_dir")" base="$(basename "$working_dir")" output_dir="$BACKUP_ROOT/$project" @@ -91,7 +93,9 @@ backup_project() { mkdir -p "$output_dir" cd "$working_dir" compose down - if ! tar --one-file-system --zstd -cf "$output" -C "$parent" "$base"; then + read -r -a zstd_args <<<"$ZSTD_EXTRA_OPTS" + if ! tar --one-file-system -cf - -C "$parent" "$base" | zstd "${zstd_args[@]}" -o "$output"; then + rm -f -- "$output" log ERROR "Archive failed; attempting to restart $project" compose up -d || true return 1