From 6c27793897e195259653909a5b5fa21d9d99c3ea Mon Sep 17 00:00:00 2001 From: ge Date: Sun, 15 May 2022 02:13:05 +0300 Subject: [PATCH] feat: Add backup.sh --- src/baf | 66 ------------ src/bafscript | 136 +++++++++++++++++++++++++ src/lib/backup.sh | 251 ++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 387 insertions(+), 66 deletions(-) delete mode 100755 src/baf create mode 100755 src/bafscript create mode 100644 src/lib/backup.sh diff --git a/src/baf b/src/baf deleted file mode 100755 index 5fa8527..0000000 --- a/src/baf +++ /dev/null @@ -1,66 +0,0 @@ -#! /usr/bin/env bash - -# baf -- backup automation micro-framework. -# Copyright (c) 2022 ge -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . - -baf_version='0.0.0' - -BAFLIB="./lib" - -# Source library -. ${BAFLIB}/* - -print_help() { - cat <<- EOF - Backup files and databases. - - Usage: $0 [-hv] .. - - Options: - -h, --help print this help message and exit. - -v, --version print version and exit. - EOF -} - -# Print help if no arguments passed -[[ "$@" ]] || { print_help; exit 1; } - -# Transform long options to short ones -for arg in "$@"; do - shift - case "$arg" in - --help) set -- "$@" "-h";; - --version) set -- "$@" "-v";; - *) set -- "$@" "$arg";; - esac -done - -# Parse short options -while getopts ":hvS" opt; do - case "$opt" in - h) print_help; exit 0;; - v) echo "$buf_version"; exit 0;; - *) echo "Unknown option $opt" >&2; exit 1;; - esac -done - -# Parse positional arguments -shift $(($OPTIND - 1)) -while (( "$#" )); do - case "$1" in - *) posargs+=("$1"); shift;; # Save args - esac -done diff --git a/src/bafscript b/src/bafscript new file mode 100755 index 0000000..b28d92c --- /dev/null +++ b/src/bafscript @@ -0,0 +1,136 @@ +#! /usr/bin/env bash + +# bafscript -- backup automation micro-framework. +# Copyright (c) 2022 ge +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +__version='0.0.0' +__config= +__log_file='./log.txt' +__tar_options='-czf' +__name_date_fmt='_%Y%m%d-%H%M' + +if [ -n "$BAFLIB" ]; then + __library="$BAFLIB" +else + __library='./lib' +fi + +# Source library +for file in "$__library"/*; do + . "$file" +done + +print_help() { + cat <<- EOF +Backup files and databases. + +Usage: $0 [-cvlhV] ARGUMENTS.. + +Options: + -c, --config config file. + -v, --verbose verbose output. + -l, --log-file log file. + -h, --help print this help messagea and exit. + -V, --version print version and exit. + +Environment: + BAFLIB path to baf library [current: $__library] +EOF +} + +# ---------------------------------------------------------- # +# * CLI Arguments Parser # +# ---------------------------------------------------------- # + +optval() { + # GNU-style CLI options parser. + # + # Parse --opt VAL and --opt=VAL options. + # Requires 2 arguments: $1, $2. + # Return variables: + # opt option name. + # val option value. + # sft value for shift. + + if [[ "$1" =~ .+=.+ ]]; then + opt="${1%%=*}"; val="${1#*=}"; sft=1 + elif [[ ! "$1" =~ .+=$ ]] && [ "$2" ] && [ "${2:0:1}" != "-" ]; then + opt="$1"; val="$2"; sft=2 + else + opt="$1" + if [[ "$1" =~ .+=$ ]]; then opt="${1:0: -1}"; fi + echo "Error: Missing argument for $opt" >&2; exit 1 + fi +} + +# Print help if no arguments passed +[[ "$#" == 0 ]] && { print_help; exit 1; } + +# Split combined short options, e.g. '-abc' to '-a' '-b' '-c' +for args in "$@"; do + shift + case "$args" in + --*) + set -- "$@" "$args";; # save long options + -*) + args="$(echo "${args:1}" | grep -o . | xargs -I {} echo -n '-{} ')" + set -- "$@" $args;; + *) + set -- "$@" "$args";; # save positional arguments + esac +done + +# Final arguments parser +while (( "$#" )); do + case "$1" in + -c|--config|--config=*) optval "$1" "$2"; __config="$val"; shift "$sft";; + -v|--verbose) __verbose=1; shift;; + -l|--log-file|--log-file=*) optval "$1" "$2"; __log_file="$val"; shift "$sft";; + -h|--help) print_help; exit 1;; + -V|--version) echo "$__version"; exit 0;; + -*) echo "Error: Unknown option: $1" >&2; exit 1;; + *) __args+=("$1"); shift;; # Save positional args + esac +done + +# ---------------------------------------------------------- # +# * Do backups # +# ---------------------------------------------------------- # + +for script in "${__args[@]}"; do + source_script "$script" + + # Initialise variables + __user_script="$script" + backups=() # Array of created backups, contains full pathes + + # Run prepare() before all if set + if is_function_set prepare; then + prepare + fi + + # Run user defined backup() if set or builtin_backup() + if is_function_set backup; then + backup + else + builtin_backup + fi + + # Run post backup function if set + if is_function_set finalise; then + finalise + fi +done diff --git a/src/lib/backup.sh b/src/lib/backup.sh new file mode 100644 index 0000000..8ba340c --- /dev/null +++ b/src/lib/backup.sh @@ -0,0 +1,251 @@ +#! /usr/bin/env bash + +# backup.sh - functions for backup processing. +# Copyright (c) 2022 ge +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +process_source() { + # Run handler function for source URI by scheme. + # + # Usage: process_source URI + + local uri + local scheme + local handler + + uri="$1" + scheme="${uri%%:*}" + + case "$scheme" in + file) handler='backup_files';; + mysql) handler='backup_mysql';; + postgres) handler='backup_postgres';; + sqlite) handler='backup_sqlite';; + *) echo "Error: Unsupported URI scheme: $scheme" >&2; exit 1;; + esac + + # Run handler function + "$handler" "$uri" +} + +process_target() { + # Run handler function for target URI by scheme. + # + # Usage: process_target URI + + local uri + local scheme + local handler + + uri="$1" + scheme="${uri%%:*}" + + case "$scheme" in + file) handler='transfer_files';; + ftp) handler='transfer_ftp';; + sftp) handler='transfer_sftp';; + rsync) handler='transfer_rsync';; + s3) handler='transfer_s3';; + sj) handler='transfer_sj';; + swift) handler='transfer_swift';; + dav) handler='transfer_dav';; + davs) handler='transfer_davs';; + *) echo "Error: Unsupported URI scheme: $scheme" >&2; exit 1;; + esac + + # Run handler function + "$handler" "$uri" +} + +builtin_backup() { + # Backup function. + # + # Usage: builtin_backup + + for source in "${sources[@]}"; do + process_source "$source" + done + + for target in "${targets[@]}"; do + process_target "$target" + done +} + +# ---------------------------------------------------------- # +# * Backup functions # +# ---------------------------------------------------------- # + +backup_files() { + # Backup local files with tar(1). Handle 'file' URI scheme. + # + # Usage: backup_files URI + + local uri + local src_path + local dst_path + local archive + local file_ext + + uri="$1" + dst_path="$__main_target_path" + + parse_uri "$uri" + + if [ -f "$path" ] || [ -d "$path" ]; then + src_path="$path" + else + echo "Error: Path '$path' from URI '$uri' does not exists" >&2 + exit 1 + fi + + is_installed tar # Exit if tar is not installed + + # Overwrire __tar_options + if [ -n "$tar_options" ]; then + __tar_options="$tar_options" + fi + + # TODO выбор сжатия, можно в переменной __tar_options заменять букву z на + # другую для соответствующего сжатия + file_ext=.tar.gz + + archive="${dst_path}/$(gen_backup_name "$file_ext")" + + tar "$__tar_options" "$archive" "$src_path" |& log -p + + # Append path to 'backups' array + backups+=("$archive") +} + +backup_mysql() { + echo Not implemented >&2; exit 1 +} + +backup_postgres() { + echo Not implemented >&2; exit 1 +} + +backup_sqlite() { + echo Not implemented >&2; exit 1 +} + +# ---------------------------------------------------------- # +# * Functions for targets processing # +# ---------------------------------------------------------- # + +transfer_files() { + # Transfer files to another location from __main_target_path using cp(1) + # + # Usage: transfer_files URI + + local uri + local dst_path + + uri="$1" + + if [[ "$uri" == "$__main_target" ]]; then + : # Do nothing. Source and destination is the same + else + # Copy backups to another destination + parse_uri "$uri" + + if [ -f "$path" ] || [ -d "$path" ]; then + dst_path="$path" + else + echo "Error: Path '$path' from URI '$uri' does not exists" >&2 + exit 1 + fi + + # Copy files preserving metadata + for backup in "${backups[@]}"; do + cp --archive "$backup" "$dst_path" + done + fi +} + +transfer_ftp() { + echo Not implemented >&2; exit 1 +} + +transfer_sftp() { + echo Not implemented >&2; exit 1 +} + +transfer_rsync() { + echo Not implemented >&2; exit 1 +} + +transfer_s3() { + echo Not implemented >&2; exit 1 +} + +transfer_sj() { + echo Not implemented >&2; exit 1 +} + +transfer_swift() { + echo Not implemented >&2; exit 1 +} + +transfer_dav() { + echo Not implemented >&2; exit 1 +} + +transfer_davs() { + echo Not implemented >&2; exit 1 +} + +# ---------------------------------------------------------- # +# * Helper functions # +# ---------------------------------------------------------- # + +is_installed() { + # Check if the program is installed. + # See good answer: https://stackoverflow.com/a/677212 + # + # Usage: is_installed COMMAND + + local cmd + + cmd="$1" + + if ! command -v "$cmd" >/dev/null 2>&1; then + echo "Error: Command $cmd not found." \ + "Please install $cmd or check your PATH if it's actually installed." >&2 + exit 1 + fi +} + +gen_backup_name() { + # Generate backup file name. Return (echo) string. + # + # Usage: gen_backup_name NAME_EXT + + local prefix + local name + local date_fmt + local name_ext + + [ -n "$name_prefix" ] || { prefix="${__user_script}_"; } + name="$(basename $path)" # 'path' is variable parsed from URI + name_ext="$1" + + # Overwrite __name_date_fmt + if [ -n "$name_date_fmt" ]; then + __name_date_fmt="$name_date_fmt" + fi + + date +"${prefix}${name}${__name_date_fmt}${name_ext}" +}