From 5ecba558fd3a4dece44f8796f1989e11bb43bc84 Mon Sep 17 00:00:00 2001 From: KAAAsS Date: Sun, 13 Feb 2022 19:48:02 +0800 Subject: [PATCH] v1.0 --- .gitignore | 1 + README.md | 3 + backup.conf.example | 11 ++ webdav_backup.sh | 339 ++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 354 insertions(+) create mode 100644 .gitignore create mode 100644 README.md create mode 100644 backup.conf.example create mode 100755 webdav_backup.sh diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..f239462 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +backup.conf diff --git a/README.md b/README.md new file mode 100644 index 0000000..2f9e73c --- /dev/null +++ b/README.md @@ -0,0 +1,3 @@ +# webdav_backup_sh + +用于备份某路径至 WebDav 的 Bash 脚本 diff --git a/backup.conf.example b/backup.conf.example new file mode 100644 index 0000000..cf3b53a --- /dev/null +++ b/backup.conf.example @@ -0,0 +1,11 @@ +# WebDav 服务器 +server="https://localhost" + +# 用户 +user="" + +# 密码 +pass="" + +# 忽略 SSL 证书校验 +ignore_cert=false diff --git a/webdav_backup.sh b/webdav_backup.sh new file mode 100755 index 0000000..6106e7e --- /dev/null +++ b/webdav_backup.sh @@ -0,0 +1,339 @@ +#!/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 "-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" +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" -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" -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$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() { + [[ $_try_return -eq 0 ]] && eval $({ ! _1=$({ _0=$($@); } 2>&1; echo -n "_try_out='$_0' _try_return=$? " >&2); echo -n "_try_err='$_1'"; } 2>&1) +} + +__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:u:p:k:I +LONGOPTS=config,verbose,help,version,prefix:,server:,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 + ;; + -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