#!/bin/bash SCRIPT_DESC="WebDav 备份脚本" SCRIPT_VERSION=1.0 SCRIPT_AUTHOR=KAAAsS show_help() { __opt() { printf " %-30s%s\n" "$1" "$2"; } echo "用法:" echo " $0 待备份文件路径" echo " $0 [选项] [--] 待备份文件路径" echo echo $SCRIPT_DESC echo echo "选项:" __opt "-x, --prefix " "备份文件前缀" __opt "-s, --server " "WebDav 服务器地址,以 \"/\" 结束" __opt "-P, --server-path " "WebDav 备份路径,以 \"/\" 结束" __opt "-u, --user " "WebDav 用户名" __opt "-p, --pass " "WebDav 密码" __opt "-k, --keeps n" "保留最近几个备份文件。默认为0,不删除备份文件" __opt "-I, --ignore-cert" "忽略 WebDav 证书" echo __opt "-c, --config" "配置文件" __opt "-v, --verbose" "打印调试信息" __opt "-h, --help" "显示此帮助" __opt "-V, --version" "显示版本" echo } #----------# # 可配置变量 # #----------# verbose=false tmpfs="/tmp" prefix="backup" server="http://your-site/dav" server_path= user="guest" pass="" backup_path=- keeps=0 retry=3 ignore_cert=false curl_extra_args= #-------- # 脚本逻辑 #-------- filename=- backup_dir=- exit_handler() { local error_code="$?" # test $error_code == 0 && return; __dbg "清理临时文件..." if [[ -f $filename ]]; then __dbg " $filename" rm -rf $filename fi if [[ -d $backup_dir ]]; then __dbg " $backup_dir" rm -rf $backup_dir fi exit "$error_code" } make_backup() { if [ ! -d $backup_path ]; then __err "待备份路径不存在!" exit 2 fi filename="$tmpfs/$prefix"_"$(date +%y%m%d_%H%M%S).tar.gz" backup_dir=$tmpfs/${prefix}_$(date +%H%M%S) if [ -d $backup_dir ]; then rm -rf $backup_dir fi __dbg "备份文件路径 $backup_path 至 $backup_dir" echo -n "打包备份路径... " mkdir $backup_dir __try cp -r $backup_path $backup_dir __try tar -czvf $filename -C $backup_dir . if __catch e; then __color $RED '错误\n' __err "打包失败: $e" exit 2 fi __color $GREEN '成功\n' __dbg "成功打包至: $filename" } upload() { for retry_count in $( seq 0 $retry ); do if [[ $retry_count -eq 0 ]]; then echo -n "上传备份文件... " else echo -n "上传备份文件: 第 $retry_count 次重试... " fi __try curl -sS --user "$user:$pass" -T $1 "$server$server_path" -f $curl_extra_args if __catch e; then __color $RED '错误\n' __err "上传失败: $e" continue fi __color $GREEN '成功\n' return 0 done __err "超过失败重试次数" exit 3 } get_file_list() { flist_html=$(curl -sS --user "$user:$pass" -X PROPFIND "$server$server_path" -f --header 'Depth: 1' $curl_extra_args) echo $flist_html | grep -Po '.+?\/\K(.+?)(?=<\/D:href>)' -o | grep $prefix | sort } delete_file() { local file=$1 for retry_count in $( seq 0 $retry ); do if [[ $retry_count -eq 0 ]]; then echo -n "删除备份 $file... " else echo -n "删除备份 $file: 第 $retry_count 次重试... " fi __try curl -sS --user "$user:$pass" -X DELETE "$server$server_path$file" -f $curl_extra_args if __catch e; then __color $RED '错误\n' __err "删除失败: $e" continue fi __color $GREEN '成功\n' return 0 done __err "超过失败重试次数" exit 3 } remove_old_backup() { echo -n "检查已有备份... " __try get_file_list if __catch e; then __err "无法取得服务器文件信息: $e" __err "删除旧备份失败!" __color $RED '错误\n' exit 4 fi __get_output file_list local bk_count=$(echo $file_list | wc -w) if [[ $bk_count -gt $keeps ]];then local del_count=`expr $bk_count - $keeps` __color $BLUE "$del_count 个过时备份\n" __dbg "现存文件列表:$file_list" # 删除文件 local del_lst=$(echo $file_list | cut -d " " -f 1-$del_count) for file in $del_lst; do delete_file $file done else __color $GREEN '无需删除\n' __dbg "现存文件列表:$file_list" fi } main() { trap exit_handler EXIT make_backup upload $filename if [[ $keeps -gt 0 ]]; then remove_old_backup fi } #------- # Utils #------- __init_util() { # DO NOT CALL ME OTHER THEN STARTUP _try_return=0 _try_err=- _try_out=- } GREEN='0;32' RED='0;31' BLUE='0;34' __color() { c=$1 shift 1 printf "\033[${c}m$@\033[0m" } __dbg() { if [ $verbose = true ]; then __color $BLUE "$@" echo fi } __err() { __color $RED "$0: $@" >&2 echo } __try() { if [[ $_try_return -eq 0 ]]; then eval $({ ! _1=$({ _0=$($@); } 2>&1; echo -n "_try_out='$_0' _try_return=$? " >&2); echo -n "_try_err='$_1'"; } 2>&1) fi } __catch() { _old_try=$_try_return _try_return=0 export $1="$_try_err" [[ $_old_try -ne 0 ]] } __get_output() { export $1="$_try_out" } #--------# # 参数解析 # #--------# set -o errexit -o pipefail -o noclobber -o nounset ! getopt --test > /dev/null if [[ ${PIPESTATUS[0]} -ne 4 ]]; then __err '环境缺少 getopt 以解析参数' exit 1 fi OPTIONS=c:vhVx:s:P:u:p:k:I LONGOPTS=config,verbose,help,version,prefix:,server:,server-path:,user:,pass:,keeps:,ignore-cert ! PARSED=$(getopt --options=$OPTIONS --longoptions=$LONGOPTS --name "$0" -- "$@") if [[ ${PIPESTATUS[0]} -ne 0 ]]; then exit 1 fi eval set -- "$PARSED" while true; do case "$1" in -c|--config) # 解析配置文件 if [ ! -f "$2" ]; then __err "配置文件不存在!" exit 1 fi source $2 shift 2 ;; -v|--verbose) verbose=true shift ;; -h|--help) show_help exit 0 ;; -V|--version) echo $SCRIPT_VERSION exit 0 ;; -x|--prefix) prefix="$2" shift 2 ;; -s|--server) server="$2" shift 2 ;; -P|--server-path) server_path="$2" shift 2 ;; -u|--user) user="$2" shift 2 ;; -p|--pass) pass="$2" shift 2 ;; -k|--keeps) keeps="$2" shift 2 ;; -I|--ignore-cert) ignore_cert=true shift ;; --) # 结束参数解析 shift break ;; *) __err "参数解析失败" exit 1 ;; esac done if [[ $ignore_cert = "true" ]]; then curl_extra_args="$curl_extra_args -k" fi if [[ $# -gt 1 ]]; then __err "仅支持备份一个路径" exit 1 elif [[ $# -eq 1 ]]; then backup_path=$1 fi if [[ $backup_path = - ]]; then __err "必须指定备份路径" exit 1 fi __init_util main