From 8dc1ec73ae594571548a3af249721225409fca3c Mon Sep 17 00:00:00 2001 From: grayhook Date: Fri, 13 Jun 2025 14:13:42 +0700 Subject: [PATCH] improve --- ebup.sh | 384 +++++++++++++++++++++++++++++++++++++--------------------------- 1 file changed, 220 insertions(+), 164 deletions(-) diff --git a/ebup.sh b/ebup.sh index 469b2bc..2bb73fe 100755 --- a/ebup.sh +++ b/ebup.sh @@ -5,199 +5,255 @@ set -euo pipefail # --- Functions --- print_usage() { - echo "Usage: $0 [backup_output_path] [--list file_with_paths] -- ..." - echo - echo "Examples:" - echo " $0 /backup/my_backup.tar.gz -- /home/user /etc" - echo " BACKUP_PATH=/backup/auto.tar.gz $0 --list dirs.txt -- /var/log" - echo " $0 /mnt/backup/nextcloud_mirror -- /srv/nextcloud" - exit 1 + echo "Usage: $0 [backup_output_path] [--list file_with_paths] [--exclude path] [--exclude-from file] -- ..." + echo + echo "Examples:" + echo " $0 /backup/my_backup.tar.gz -- /home/user /etc" + echo " BACKUP_PATH=/backup/auto.tar.gz $0 --list dirs.txt -- /var/log" + echo " $0 /mnt/backup/nextcloud_mirror -- /srv/nextcloud" + echo " $0 /backup.tgz --exclude \"/home/*/.cache\" -- /home" + exit 1 } parse_args() { - local -n _target_path=$1 - local -n _src_dirs=$2 - local -n _list_file=$3 - local -n _archive_type=$4 - - local positional=() - local in_sources=0 - - while [[ $# -gt 4 ]]; do - case "${5}" in - --list) - shift 5 - _list_file="${1:-}" - if [[ -z "$_list_file" || ! -f "$_list_file" ]]; then - echo "Error: list file '$_list_file' not found." >&2 - print_usage - exit 1 - fi - shift - ;; - --) - in_sources=1 - shift - ;; - *) - if [[ $in_sources -eq 1 ]]; then - _src_dirs+=("$5") - else - positional+=("$5") - fi - shift - ;; + local -n _target_path=$1 + local -n _src_dirs=$2 + local -n _list_file=$3 + local -n _archive_type=$4 + local -n _exclude_paths=$5 + local -n _exclude_file=$6 + + local positional=() + local in_sources=0 + shift 6 + + while [[ $# -gt 0 ]]; do + case "$1" in + --list) + shift + _list_file="${1:-}" + if [[ -z "$_list_file" || ! -f "$_list_file" ]]; then + echo "Error: list file '$_list_file' not found." >&2 + print_usage + fi + shift + ;; + --exclude) + shift + _exclude_paths+=("$1") + shift + ;; + --exclude-from) + shift + _exclude_file="$1" + if [[ ! -f "$_exclude_file" ]]; then + echo "Error: exclude-from file '$_exclude_file' not found." >&2 + print_usage + fi + shift + ;; + --) + in_sources=1 + shift + ;; + *) + if [[ $in_sources -eq 1 ]]; then + _src_dirs+=("$1") + else + positional+=("$1") + fi + shift + ;; + esac + done + + if [[ ${#positional[@]} -gt 1 ]]; then + echo "Error: Too many positional arguments before --" >&2 + print_usage + elif [[ ${#positional[@]} -eq 1 ]]; then + _target_path="${positional[0]}" + else + _target_path="${BACKUP_PATH:-}" + fi + + if [[ -z "$_target_path" ]]; then + echo "Error: No target path provided and BACKUP_PATH is not set." >&2 + print_usage + fi + + case "$_target_path" in + *.tar) _archive_type="tar";; + *.tar.gz|*.tgz) _archive_type="targz";; + *.tar.xz|*.txz) _archive_type="tarxz";; + *.tar.bz2|*.tbz) _archive_type="tarbz2";; + *.7z) _archive_type="7z";; + *) _archive_type="NONE";; esac - done - - if [[ ${#positional[@]} -gt 1 ]]; then - echo "Error: Too many positional arguments before --" >&2 - print_usage - elif [[ ${#positional[@]} -eq 1 ]]; then - _target_path="${positional[0]}" - else - _target_path="${BACKUP_PATH:-}" - fi - - if [[ -z "$_target_path" ]]; then - echo "Error: No target path provided and BACKUP_PATH is not set." >&2 - print_usage - exit 1 - fi - - case "$_target_path" in - *.tar) _archive_type="tar";; - *.tar.gz|*.tgz) _archive_type="targz";; - *.tar.xz|*.txz) _archive_type="tarxz";; - *.tar.bz2|*.tbz) _archive_type="tarbz2";; - *.7z) _archive_type="7z";; - *) _archive_type="NONE";; - esac } load_dirs_from_list_file() { - local -n _src_dirs=$1 - local list_file="$2" - - if [[ -n "$list_file" ]]; then - while IFS= read -r line || [[ -n "$line" ]]; do - [[ -z "$line" || "$line" =~ ^# ]] && continue - _src_dirs+=("$line") - done < "$list_file" - fi + local -n _src_dirs=$1 + local list_file="$2" + + if [[ -n "$list_file" ]]; then + while IFS= read -r line || [[ -n "$line" ]]; do + [[ -z "$line" || "$line" =~ ^# ]] && continue + _src_dirs+=("$line") + done < "$list_file" + fi } validate_src_dirs() { - local -n _src_dirs=$1 - local valid_dirs=() - for dir in "${_src_dirs[@]}"; do - if [[ -d "$dir" ]]; then - valid_dirs+=("$dir") - else - echo "Warning: '$dir' is not a valid directory. Skipping." >&2 - fi - done - _src_dirs=("${valid_dirs[@]}") + local -n _src_dirs=$1 + local valid_dirs=() + for dir in "${_src_dirs[@]}"; do + if [[ -d "$dir" ]]; then + valid_dirs+=("$dir") + else + echo "Warning: '$dir' is not a valid directory. Skipping." >&2 + fi + done + _src_dirs=("${valid_dirs[@]}") - if [[ ${#_src_dirs[@]} -eq 0 ]]; then - echo "Error: No valid source directories to back up." >&2 - print_usage - exit 1 - fi + if [[ ${#_src_dirs[@]} -eq 0 ]]; then + echo "Error: No valid source directories to back up." >&2 + print_usage + fi } init_tmp_dir() { - mktemp -d -t backup-tmp-XXXXXX + mktemp -d -t backup-tmp-XXXXXX } count_files() { - local src_dirs=("$@") - local count=0 - echo "Counting directories: " "${src_dirs[@]}" 1>&2 - for dir in "${src_dirs[@]}"; do - echo "Counting directory: " "$dir" 1>&2 - for subdir in `find . -maxdepth 2` - count=$((count + $(find "$dir" -xdev | wc -l))) - done - echo "$count" + local exclude_args=() + local src_dirs=() + while [[ "$1" != "--" ]]; do + exclude_args+=("$1") + shift + done + shift # skip the "--" + src_dirs=("$@") + + local count=0 + for dir in "${src_dirs[@]}"; do + local find_cmd=(find "$dir" -xdev) + if [[ ${#exclude_args[@]} -gt 0 ]]; then + find_cmd+=(\( "${exclude_args[@]}" -prune \) -o -print) + fi + count=$((count + $("${find_cmd[@]}" | wc -l))) + done + echo "$count" } run_rsync_backup() { - local src_dirs=("${!1}") - local dest_path="$2" - local file_count="$3" - - echo "Starting backup..." - for dir in "${src_dirs[@]}"; do - base="$(basename "$dir")" - dest_dir="$dest_path/$base" - rsync -Aavx "$dir"/ "$dest_dir"/ | pv -ltps "$file_count" > /dev/null - done - echo "Rsync completed." + local src_dirs=(${!1}) + local dest_path="$2" + local file_count="$3" + local -n exclude_paths=$4 + local exclude_file="$5" + + echo "Starting backup..." + + local rsync_opts=("-Aavx") + for e in "${exclude_paths[@]}"; do + rsync_opts+=("--exclude=$e") + done + if [[ -n "$exclude_file" ]]; then + rsync_opts+=("--exclude-from=$exclude_file") + fi + + for dir in "${src_dirs[@]}"; do + base="$(basename "$dir")" + dest_dir="$dest_path/$base" + rsync "${rsync_opts[@]}" "$dir"/ "$dest_dir"/ | pv -ltps "$file_count" > /dev/null + done + echo "Rsync completed." } compress_backup() { - local tmp_dir="$1" - local target_path="$2" - local archive_type="$3" - - echo "Creating archive: $target_path" - case "$archive_type" in - targz) - tar czf - -C "$tmp_dir" . | pv -s "$(du -sb "$tmp_dir" | awk '{print $1}')" > "$target_path" - ;; - tar) - tar cf - -C "$tmp_dir" . | pv -s "$(du -sb "$tmp_dir" | awk '{print $1}')" > "$target_path" - ;; - tarxz) - tar cJf - -C "$tmp_dir" . | pv -s "$(du -sb "$tmp_dir" | awk '{print $1}')" > "$target_path" - ;; - tarbz2) - tar cjf - -C "$tmp_dir" . | pv -s "$(du -sb "$tmp_dir" | awk '{print $1}')" > "$target_path" - ;; - 7z) - 7z a -mx=9 "$target_path" "$tmp_dir"/* - ;; - *) - echo "Unsupported archive type or no compression." >&2 - return 1 - ;; - esac - echo "Archive created successfully." + local tmp_dir="$1" + local target_path="$2" + local archive_type="$3" + + echo "Creating archive: $target_path" + case "$archive_type" in + targz) + tar czf - -C "$tmp_dir" . | pv -s "$(du -sb "$tmp_dir" | awk '{print $1}')" > "$target_path" + ;; + tar) + tar cf - -C "$tmp_dir" . | pv -s "$(du -sb "$tmp_dir" | awk '{print $1}')" > "$target_path" + ;; + tarxz) + tar cJf - -C "$tmp_dir" . | pv -s "$(du -sb "$tmp_dir" | awk '{print $1}')" > "$target_path" + ;; + tarbz2) + tar cjf - -C "$tmp_dir" . | pv -s "$(du -sb "$tmp_dir" | awk '{print $1}')" > "$target_path" + ;; + 7z) + 7z a -mx=9 "$target_path" "$tmp_dir"/* + ;; + *) + echo "Unsupported archive type or no compression." >&2 + print_usage + ;; + esac + echo "Archive created successfully." } cleanup_tmp_dir() { - local tmp_dir="$1" - if [[ -n "$tmp_dir" && -d "$tmp_dir" ]]; then - echo "Cleaning up temporary files..." - rm -rf "$tmp_dir" - echo "Done." - fi + local tmp_dir="$1" + if [[ -n "$tmp_dir" && -d "$tmp_dir" ]]; then + echo "Cleaning up temporary files..." + rm -rf "$tmp_dir" + echo "Done." + fi +} + +teardown() { + echo "Do sync..." + sync } main() { - local TARGET_PATH="" - local SRC_DIRS=() - local LIST_FILE="" - local ARCHIVE_TYPE="NONE" - - parse_args TARGET_PATH SRC_DIRS LIST_FILE ARCHIVE_TYPE "$@" - load_dirs_from_list_file SRC_DIRS "$LIST_FILE" - validate_src_dirs SRC_DIRS - - local FILE_COUNT - FILE_COUNT=$(count_files "${SRC_DIRS[@]}") - - if [[ "$ARCHIVE_TYPE" != "NONE" ]]; then - local TMP_DIR - TMP_DIR=$(init_tmp_dir) - run_rsync_backup SRC_DIRS[@] "$TMP_DIR" "$FILE_COUNT" - compress_backup "$TMP_DIR" "$TARGET_PATH" "$ARCHIVE_TYPE" - cleanup_tmp_dir "$TMP_DIR" - else - mkdir -p "$TARGET_PATH" - run_rsync_backup SRC_DIRS[@] "$TARGET_PATH" "$FILE_COUNT" - fi + local TARGET_PATH="" + local SRC_DIRS=() + local LIST_FILE="" + local ARCHIVE_TYPE="NONE" + local EXCLUDE_PATHS=() + local EXCLUDE_FILE="" + + parse_args TARGET_PATH SRC_DIRS LIST_FILE ARCHIVE_TYPE EXCLUDE_PATHS EXCLUDE_FILE "$@" + load_dirs_from_list_file SRC_DIRS "$LIST_FILE" + validate_src_dirs SRC_DIRS + + local exclude_args=() + for e in "${EXCLUDE_PATHS[@]}"; do + exclude_args+=( -path "$e" -o ) + done + if [[ -n "$EXCLUDE_FILE" ]]; then + while IFS= read -r line || [[ -n "$line" ]]; do + [[ -z "$line" || "$line" =~ ^# ]] && continue + exclude_args+=( -path "$line" -o ) + done < "$EXCLUDE_FILE" + fi + [[ ${#exclude_args[@]} -gt 0 ]] && unset 'exclude_args[${#exclude_args[@]}-1]' + + local FILE_COUNT + FILE_COUNT=$(count_files "${exclude_args[@]}" -- "${SRC_DIRS[@]}") + + if [[ "$ARCHIVE_TYPE" != "NONE" ]]; then + local TMP_DIR + TMP_DIR=$(init_tmp_dir) + run_rsync_backup SRC_DIRS[@] "$TMP_DIR" "$FILE_COUNT" EXCLUDE_PATHS "$EXCLUDE_FILE" + compress_backup "$TMP_DIR" "$TARGET_PATH" "$ARCHIVE_TYPE" + cleanup_tmp_dir "$TMP_DIR" + else + mkdir -p "$TARGET_PATH" + run_rsync_backup SRC_DIRS[@] "$TARGET_PATH" "$FILE_COUNT" EXCLUDE_PATHS "$EXCLUDE_FILE" + fi + + teardown } # --- Entry Point ---