From 961b8a902ddeb3623c26e2c72f1c12c85751c8c1 Mon Sep 17 00:00:00 2001 From: grayhook Date: Fri, 13 Jun 2025 13:19:50 +0700 Subject: [PATCH] init --- ebup.sh | 205 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 205 insertions(+) create mode 100755 ebup.sh diff --git a/ebup.sh b/ebup.sh new file mode 100755 index 0000000..469b2bc --- /dev/null +++ b/ebup.sh @@ -0,0 +1,205 @@ +#!/bin/bash + +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 +} + +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 + ;; + 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 +} + +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[@]}") + + if [[ ${#_src_dirs[@]} -eq 0 ]]; then + echo "Error: No valid source directories to back up." >&2 + print_usage + exit 1 + fi +} + +init_tmp_dir() { + 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" +} + +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." +} + +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." +} + +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 +} + +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 +} + +# --- Entry Point --- +main "$@" +