init
commit
961b8a902d
@ -0,0 +1,205 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
# --- Functions ---
|
||||
|
||||
print_usage() {
|
||||
echo "Usage: $0 [backup_output_path] [--list file_with_paths] -- <src_dir1> <src_dir2> ..."
|
||||
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 "$@"
|
||||
|
||||
Loading…
Reference in New Issue