diff --git a/Makefile b/Makefile index e073344..47a2a9f 100644 --- a/Makefile +++ b/Makefile @@ -23,8 +23,10 @@ tests: for test in $(tests_dir)/*.bats; do bats --verbose-run --print-output-on-failure "$$test"; done lint: - shellcheck $(src_dir)/boring-backup || true - shellcheck $(src_dir)/lib/* || true + shellcheck $(src_dir)/boring-backup + shellcheck $(src_dir)/lib/*.sh + shellcheck $(src_dir)/lib/handlers/sources/*.sh + shellcheck $(src_dir)/lib/handlers/targets/*.sh docs: builddir # See rst2man(1), rst2html(1), https://docutils.sourceforge.io/docs/index.html diff --git a/docs/boring-backup.1.rst b/docs/boring-backup.1.rst index 83697e5..d7aadc0 100644 --- a/docs/boring-backup.1.rst +++ b/docs/boring-backup.1.rst @@ -20,7 +20,7 @@ backup files and databases. Описание -------- -boring-backup - это расширяемая утилита для резервного копирования на основе сценариев Bash. По умолчанию предусмотрено создание резервных копий файлов с помощью ``tar`` и дампов баз данных MySQL/MariaDB и PostgreSQL. +boring-backup - это расширяемая утилита для резервного копирования на основе сценариев Bash. Опции ----- @@ -35,28 +35,26 @@ boring-backup - это расширяемая утилита для резерв Быстрый старт ------------- -boring-backup можно рассматривать как небольшой фреймворк/библиотеку для создания сценариев резервного копирования. boring-backup не реализует собственный язык сценариев, а полагается на Bash. В любом варианте применения утилита требует написания сценария для выполнения бэкапа. - -В сценариях можно использовать переменные и функции. Сценарий импортируется в основной скрипт с помощью команды ``source`` (см. ``bash``\(1) п. SHELL BUILTIN COMMANDS). +boring-backup можно рассматривать как фреймворк или библиотеку для создания сценариев резервного копирования. Сценарии должны содержать валидный код на Bash. Сценарий импортируется в основной скрипт с помощью команды ``source`` (см. ``bash``\(1) п. SHELL BUILTIN COMMANDS). Простейший сценарий резервного копирования выглядит следующим образом:: sources=('file:/home/user') targets=('file:/var/backup') -Здесь массивы `sources` и `targets` определяют точки или поинты (`points`) резервного копирования — источники (`sources`) и назначения (`targets`). Это основные сущности, с которыми работает boring-backup. Вот некоторые особенности поинтов: +Здесь массивы `sources` и `targets` определяют точки, они же поинты (`points`) резервного копирования: источники (`sources`) и назначения (`targets`). Это основные сущности, с которыми работает boring-backup. Вот некоторые особенности поинтов: -- Все поинты указываются в формате URI. Описание URI также есть в этой документации ниже. +- Все поинты указываются в формате URI. - Поинты могут указывать как на локальные (размещённые на текущей машине), так и на удалённые (размещённые на удалённой машине) ресурсы. - Можно указать как несколько источников, так и несколько назначений. -Для выполнения бэкапа директорий или отдельных файлов применяется схема URI `file`. В схеме `file` нельзя указать директорию, размещённую на удалённом хранилище (за исключением случая, когда удалённое хранилище примонтировано как файловая система), для этого используйте другие схемы. В примере выше будет выполнена резервная копия локальной директории /home/user в другую локальную директорию /var/backup. Поскольку в сценарии из примера нет ничего, кроме указания точек `sources` и `targets`, бэкап будет выполнен с параметрами по умолчанию: директория /home/user будет заархивирована с помощью утилиты ``tar``\(1), архив будет сжат с помощью утилиты ``gzip``\(1) и помещён в директорию /var/backup. Имя архива будет сложено из строки:: +Для выполнения бэкапа директорий или отдельных файлов применяется схема URI `file`. В схеме `file` нельзя указать директорию, размещённую на удалённом хранилище (за исключением случая, когда удалённое хранилище примонтировано как файловая система), для этого используйте другие схемы. В примере выше будет выполнена резервная копия локальной директории /home/user в другую локальную директорию /var/backup. Поскольку в сценарии из примера нет ничего, кроме указания точек `sources` и `targets`, бэкап будет выполнен с параметрами по умолчанию: директория /home/user будет заархивирована с помощью утилиты ``tar``\(1) и помещён в директорию /var/backup. Имя архива будет сложено из строки:: - ${prefix}${name}${date_fmt}${name_ext} + ${name_prefix}${name}${name_date_fmt}${name_suffix}${name_ext} Описание переменных см. в разделе ПЕРЕМЕННЫЕ. Пример имени файла:: - example.sh_example_20220515-0953.tar.gz + example.sh_example_2022.05.15-0953.tar Обзор URI --------- @@ -101,8 +99,9 @@ Perl 5:: `````````````````````````````````````````````````` file - Может содержать путь к локальному файлу или директории. Файлы архивируются при помощи ``tar`` и по умолчанию сжимаются с помощью ``gzip``. Примеры:: + Может содержать путь к локальному файлу или директории. Файлы архивируются при помощи ``tar`` и по умолчанию не сжимаются. Cжатие архива может быть включено через переменную `compression`. Примеры:: + /var/www/www-root/data file:/var/www/html file:///home/jhon/cool_stuff.txt @@ -115,17 +114,17 @@ mysql mysql://[username[:password]@]hostname[:port]/database - С помощью утилиты ``mysqldump``\(1) формируется дамп в формате SQL, файл по умолчанию сжимается утилитой ``gzip``. + С помощью утилиты ``mysqldump``\(1) формируется дамп в формате SQL. Файл по умолчанию не сжимается, но сжатие может быть включено через переменную `compression`. См. также ``handler::mysqldump`` postgres - Аналогично `mysql`, но для PostgreSQL. Для создания дампа используется ``pg_dump``\(1), файлы по умолчанию сохраняются с расширением .psql.gz. + Аналогично `mysql`, но для PostgreSQL. Для создания дампа используется ``pg_dump``\(1) с расширением .psql. Файл по умолчанию не сжимается, но сжатие может быть включено через переменную `compression`. См. также ``handler::pg_dump`` sqlite - Схема для баз данных SQLite. Ничем не отличается от `file`, добавлена для наглядного обозначения, что источником является БД SQLite, а не иной файл. + Схема для баз данных SQLite. Мало отличается от `file`, добавлена для наглядного обозначения, что источником является БД SQLite, а не иной файл. См. также ``handler::sqlite`` @@ -133,7 +132,7 @@ sqlite `````````````````````````````````````````````````` file - В контексте `targets` указывает директорию для сохранения бэкапов. В массиве `targets` обязательно должен быть хотя бы один поинт со схемой `file`. Этот поинт будет использован как основной и сохранён в переменные ``__main_target`` (содержит URI) и ``__main_target_path`` (path). Если в массиве присутствует несколько поинтов со схемой `file`, то в качестве основного будет выбран первый по порядку поинт. Все бэкапы первоначально будут сохраняться в директорию ``__main_target_path`` и затем копироваться в другой таргет с помощью соответствующего обработчика. В случае копировани из одной директории в другую используется утилита ``cp``\(1). + В контексте `targets` указывает директорию для сохранения бэкапов. В массиве `targets` обязательно должен быть хотя бы один поинт со схемой `file`. Этот поинт будет использован как основной и сохранён в переменные ``__main_target`` (содержит URI) и ``__main_target_path`` (содержит компонент URI path). Если в массиве присутствует несколько поинтов со схемой `file`, то в качестве основного будет выбран первый по порядку поинт. Все бэкапы первоначально будут сохраняться в директорию ``__main_target_path`` и затем копироваться в другой таргет с помощью соответствующего обработчика. В случае копировани из одной директории в другую используется утилита ``cp``\(1). Избегайте указания в одном сценарии таргетов, которые ведут одновременно на примонтированный к локальной машине сетевой диск и другое удалённое хранилище. В таком случае архивы будут создаваться на сетевом диске и далее снова копироваться по сети, что будет очень медленно. @@ -165,11 +164,11 @@ dav, davs boring-backup предполагает, что для всех резервных копий необходимо создавать архивы. Поэтому вам нужно следить за тем, чтобы в локальном хранилище всегда хватало дискового пространства для создания новых архивов. boring-backup также не удаляет старые архивы и вам также надо позаботиться об удалении устаревших резервных копий. Изменить это поведение можно с помощью пользовательских функций в сценариях. В этом разделе речь пойдёт о поведении, которое установлено по умолчанию. См. функции ``builtin_backup``, ``process_sources``, ``process_targets``. -Базовая концепция строится на том, что существует несколько точек источников и несколько точек назначения. Создаётся резервная копия источника и копируется в точку назначения. +Базовая концепция строится на том, что существует несколько источников и несколько точек назначения. Создаётся резервная копия источника и копируется в точку назначения. Для всех источников в сценарии выполняется локальная резервная копия. После этого локальная копия переносится в удалённые точки назначения, если они заданы. Копирование локального бэкапа будет осуществлёно столько раз, сколько точек назначения задано. В каждом сценарии обязательно должен быть задан хотя бы один источник и одна точка назначения. При этом в списке точек назначения обязательно должна присутствовать точка со схемой `file`. Именно в неё будут сохранены архивы. -Пример. Имеется следующий сценарий:: +Например, имеется следующий сценарий:: sources=( 'mysql://wordpress:1jobrRRjtLYs@localhost/wordpress' @@ -212,80 +211,89 @@ WebDAV ``backups`` Массив со списком созданных бэкапов. Содержит абсолютные пути к файлам. Заполняется функциями-обработчиками `sources`. - Тип: массив - Умолчание: () + | Тип: массив + | Умолчание: () ``errors`` Массив с текстами ошибок. Заполняется функцией ``err`` с опцией ``-a``. - Тип: массив - Умолчание: () + | Тип: массив + | Умолчание: () ``log_date_fmt`` Формат даты в логе. См. ``date``\(1). - Тип: строка - Умолчание: %d/%b/%Y:%H:%M:%S %z + | Тип: строка + | Умолчание: %d/%b/%Y:%H:%M:%S %z ``name_prefix`` Префикс имени файла. Может быть задан в скрипте. - Тип: строка - Умолчание: имя_скрипта\_ + | Тип: строка + | Умолчание: имя_скрипта\_ ``name`` Соответствует имени архивируемой директории/файла полученного из `path` с помощью утилиты ``basename``\(1). - Тип: строка - Умолчание: нет + | Тип: строка + | Умолчание: нет ``name_date_fmt`` Дата, интерпретируемая утилитой ``date``\(1). - Тип: строка - Умолчание: _%Y%m%d-%H%M + | Тип: строка + | Умолчание: _%Y%m%d + +``name_suffix`` + Суффикс, добавляемый к имени файла перед расширением. Строка интерпретируется утилитой ``date``. + + | Тип: строка + | Умолчание: -%H%M ``name_ext`` Расширение имени файла, соответствующее формату архива. - Тип: строка - Умолчание: .tar.gz + | Тип: строка + | Умолчание: .tar.gz ``tar_options`` Опции ``tar``. См. ``tar``\(1). - Тип: строка - Умолчание: -acf + | Тип: строка + | Умолчание: -acf ``tar_exclude`` Список имён для опции ``tar`` ``--exclude``. - Тип: массив - Умолчание: нет + | Тип: массив + | Умолчание: нет ``compression`` - В контексте ``tar`` работа этой фичи базируется на опции ``tar`` ``--auto-compress``. Переменная может принимать значения, в соответствии с табилцей ниже. Если переменная имеет значение, отличное от перечисленныых, то будет использован ``gzip``. + В контексте ``tar`` работа этой фичи базируется на опции ``tar`` ``--auto-compress``. Переменная может принимать значения, в соответствии с табилцей ниже. Если переменная имеет значение, отличное от перечисленныых, то будет использован ``gzip``. Если переменная пуста или не задана, то сжатие будет отключено. + + | Тип: строка + | Умолчание: нет - ======================== ================ ======== - Значение tar_compression Расширение файла Утилита - ======================== ================ ======== - gzip, gz .tar.gz gzip - tgz .tgz gzip - taz .taz gzip - compress, Z .tar.Z compress - taZ .taZ compress - bzip2, bz2 .tar.bz2 bzip2 - tz2 .tz2 bzip2 - tbz2 .tbz2 bzip2 - tbz .tbz bzip2 - lzip, lz .tar.lz lzip - lzma .tar.lzma lzma - tlz .tlz lzma - lzop, lzo .tar.lzo lzop - xz .tar.xz xz - zstd, zst .tar.zst zstd - tzst .tzst zstd - ======================== ================ ======== + ==================== ================ ======== + Значение compression Расширение файла Утилита + ==================== ================ ======== + gzip, gz .tar.gz gzip + tgz .tgz gzip + taz .taz gzip + compress, Z .tar.Z compress + taZ .taZ compress + bzip2, bz2 .tar.bz2 bzip2 + tz2 .tz2 bzip2 + tbz2 .tbz2 bzip2 + tbz .tbz bzip2 + lzip, lz .tar.lz lzip + lzma .tar.lzma lzma + tlz .tlz lzma + lzop, lzo .tar.lzo lzop + xz .tar.xz xz + zstd, zst .tar.zst zstd + tzst .tzst zstd + ==================== ================ ======== Функции ------- diff --git a/src/boring-backup b/src/boring-backup index e20e0c5..a7220c6 100755 --- a/src/boring-backup +++ b/src/boring-backup @@ -20,21 +20,17 @@ __version='0.0.0' __config= __verbose= __log_file='./log.txt' -__log_date_fmt='%d/%b/%Y:%H:%M:%S %z' __pid_file='/tmp/boring-backup.pid' -__tar_options='-acf' -__tar_exclude= -__name_date_fmt='_%Y%m%d-%H%M' -__compression= __errors_count=0 __errors_file='/tmp/boring-backup.errors' -echo $__errors_count > $__errors_file +echo "$__errors_count" > "$__errors_file" LIBRARY="${LIBRARY:-./lib}" # Source library if [ -f "$LIBRARY/lib.sh" ]; then + # shellcheck disable=SC1091 . "$LIBRARY/lib.sh" else echo "Error: Cannot source library from $LIBRARY" >&2 @@ -69,7 +65,7 @@ optval() { # # Parse --opt VAL and --opt=VAL options. # Requires 2 arguments: $1, $2. - # Return variables: + # Set variables: # opt option name. # val option value. # sft value for shift. @@ -96,7 +92,8 @@ for args in "$@"; do set -- "$@" "$args";; # save long options -*) args="$(echo "${args:1}" | grep -o . | xargs -I {} echo -n '-{} ')" - set -- "$@" $args;; + # shellcheck disable=SC2086 + set -- "$@" $args;; # 'args' must be unquoted! *) set -- "$@" "$args";; # save positional arguments esac @@ -123,20 +120,22 @@ done log "Backup STARTED" # Check PID file -if [ -e $__pid_file ]; then - if [ -z "$(ps ax -o pid | grep "$(cat $__pid_file)")" ]; then - err "Process $(cat $__pid_file) died." - rm $__pid_file +if [ -e "$__pid_file" ]; then + # shellcheck disable=SC2009 + # shellcheck disable=SC2143 + if [ -z "$(ps ax -o pid | grep "$(cat "$__pid_file")")" ]; then + err "Process $(cat "$__pid_file") died." + rm "$__pid_file" else - err -e "Process $(cat $__pid_file) still running."; + err -e "Process $(cat "$__pid_file") still running."; fi fi # Touch PID file -echo $$ > $__pid_file +echo "$$" > "$__pid_file" # Scripts counter. -__count=${#__args[@]} # count +__count="${#__args[@]}" # count __iter=1 # iterator # Startup log. @@ -144,18 +143,22 @@ date +'Start: %d %b %Y %T %z' log -p "Library path: $LIBRARY" log -p "Log file: $__log_file" log -p "Configuration file: $([ "$__config" ] || echo not specified && echo "$__config")" -log "Scripts to process (${__count}): ${__args[@]}" +log "Scripts to process (${__count}): ${__args[*]}" for script in "${__args[@]}"; do # Initialise variables __user_script="$script" + + # shellcheck disable=SC2034 backups=() # Array of created backups, contains full pathes + # shellcheck disable=SC2034 errors=() # Array of error messages written by err() function # Source scripts source_script "$script" # Config can ovewrite script functions and variables + # shellcheck source=/dev/null [ -n "$__config" ] && . "$__config" echo @@ -209,7 +212,7 @@ if [[ "$(cat $__errors_file)" != '0' ]]; then echo log -p "Backup [Failed]: Process finished with $(cat $__errors_file) errors."\ "See $__log_file for info." - rm $__errors_file + rm "$__errors_file" else echo -e "\nBackup [Done]" fi @@ -217,4 +220,4 @@ fi log "Backup FINISHED" # Remove PID file -rm $__pid_file +rm "$__pid_file" diff --git a/src/lib/backup.sh b/src/lib/backup.sh index 539a981..ae1b05c 100644 --- a/src/lib/backup.sh +++ b/src/lib/backup.sh @@ -37,7 +37,9 @@ process_source() { mysql) handler='handler::mysqldump';; postgres) handler='handler::pg_dump';; sqlite) handler='handler::sqlite';; - *) echo "Error: $__user_script: Unsupported URI scheme: $scheme" >&2; exit 1;; + *) # shellcheck disable=SC2154 + # '__user_script' is assigned in main script. + echo "Error: $__user_script: Unsupported URI scheme: $scheme" >&2; exit 1;; esac # Run handler function @@ -84,10 +86,12 @@ builtin_backup() { # # Usage: builtin_backup + # shellcheck disable=SC2154 for source in "${sources[@]}"; do process_source "$source" done + # shellcheck disable=SC2154 for target in "${targets[@]}"; do process_target "$target" done @@ -102,21 +106,15 @@ gen_backup_name() { # # 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_prefix="${name_prefix:-${__user_script}_}" + # shellcheck disable=SC2154 + name="$(basename "$path")" # 'path' is variable parsed from URI name_ext="$1" + name_date_fmt="${name_date_fmt:-_%Y.%m.%d}" + name_suffix="${name_suffix:--%H%M}" - # Overwrite __name_date_fmt - if [ -n "$name_date_fmt" ]; then - date_fmt="$name_date_fmt" - else - date_fmt="$__name_date_fmt" - fi - - date +"${prefix}${name}${date_fmt}${name_ext}" + date +"${name_prefix}${name}${name_date_fmt}${name_suffix}${name_ext}" } diff --git a/src/lib/common.sh b/src/lib/common.sh index 626531b..2cb4557 100644 --- a/src/lib/common.sh +++ b/src/lib/common.sh @@ -31,7 +31,7 @@ log() { [[ ! -t 0 ]] || [[ "$#" == 0 ]] && message="$(cat <&0)" # Set log date format - [ "$log_date_fmt" ] || log_date_fmt="$__log_date_fmt" + log_date_fmt="${log_date_fmt:-%d/%b/%Y:%H:%M:%S %z}" while (( "$#" )); do case "$1" in @@ -44,7 +44,8 @@ log() { [[ "$print" == 1 ]] && echo -e "$message" while read -r line; do - if [[ "$line" ]]; then + if [ -n "$line" ]; then + # shellcheck disable=SC2154 printf '[%s] %s\n' "$(date +"$log_date_fmt")" "$line" >> "$__log_file" fi done <<< "$(sed -r 's/\x1B\[(([0-9]+)(;[0-9]+)*)?[m,K,H,f,J]//g' <<< "$message")" @@ -69,7 +70,8 @@ err() { case "$args" in -*) args="$(echo "${args:1}" | grep -o . | xargs -I {} echo -n '-{} ')" - set -- "$@" $args;; + # shellcheck disable=SC2086 + set -- "$@" $args;; # 'args' must be unquoted! *) set -- "$@" "$args";; # save positional arguments esac @@ -100,9 +102,10 @@ err() { # Exit if [ "$errexit" ]; then # Count errors for backup recap - __errors_count="$(cat $__errors_file)" + # shellcheck disable=SC2154 + __errors_count="$(cat "$__errors_file")" ((__errors_count++)) || true - echo $__errors_count > "$__errors_file" + echo "$__errors_count" > "$__errors_file" log -p "Backup ERROR: Exiting with previous errors" >&2; exit 1 fi @@ -113,7 +116,8 @@ try() { # # Usage: try COMMAND - if eval "$@" 2>> "$__log_file"; then + # https://www.shellcheck.net/wiki/SC2294 + if "$@" 2>> "$__log_file"; then return 0 # Success else # Count errors @@ -168,6 +172,11 @@ set_compression() { xz) file_ext='.xz'; compr_util='xz';; zstd|zst) file_ext='.zst'; compr_util='zstd';; tzst) file_ext='.zst'; compr_util='zstd';; - *) file_ext=''; compr_util='';; # No compression + *) # No compression + # shellcheck disable=SC2034 + file_ext='' + # shellcheck disable=SC2034 + compr_util='' + ;; esac } diff --git a/src/lib/handlers/sources/mysqldump.sh b/src/lib/handlers/sources/mysqldump.sh index 62eb183..840dc4f 100644 --- a/src/lib/handlers/sources/mysqldump.sh +++ b/src/lib/handlers/sources/mysqldump.sh @@ -32,12 +32,14 @@ handler::mysqldump() { local file_ext uri="$1" + # shellcheck disable=SC2154 dst_path="$__main_target_path" parse_uri "$uri" [ -z "$port" ] && port=3306 # Set default MySQL port. + # shellcheck disable=SC2154 [ -n "$__verbose" ] && echo "Dumping database '${path##*/}'"\ "owned by ${username}@${hostname} ..." | log -p @@ -61,7 +63,10 @@ handler::mysqldump() { compr_cmd="cat" fi + mysqldump_opts="${mysqldump_opts:-}" + # NOTE! mysqldump_opts and compr_cmd variables must be unquoted! + # shellcheck disable=SC2086,SC2154 try mysqldump $mysqldump_opts \ --host="$hostname" \ --port="$port" \ @@ -76,33 +81,32 @@ handler::mysqldump() { fi } -# POSTGRESQL vvv - -bk_dump_postgresql() { - # Do PostgreSQL dump. - - bk_log "Dumping database '${path##*/}' owned by ${user}@${host} (PostgreSQL) ..." - - dump_name="$entry_local"/"$(bk_get_name "$db_name" .psql.gz)" - [ "$port" ] || port=5432 # Set default PostgreSQL port. - export PGPASSWORD="$password" - - pg_dump \ - --host="$host" \ - --port="$port" \ - --dbname="${path##*/}" \ - --username="$user" \ - --no-password | gzip -c > "$dump_name" |& bk_log - - unset PGPASSWORD - - if [ -s "$dump_name" ]; then - bk_log "Dumping database '${path##*/}' owned by ${user}@${host} (PostgreSQL) [Done]" - bk_log "Dump saved as: $dump_name" - bk_upload_file "$dump_name" - - else - rm "$dump_name" - bk_err "Something went wrong. Dump size is 0 bytes. Removing $dump_name" - fi -} +# POSTGRESQL +#bk_dump_postgresql() { +# # Do PostgreSQL dump. +# +# bk_log "Dumping database '${path##*/}' owned by ${user}@${host} (PostgreSQL) ..." +# +# dump_name="$entry_local"/"$(bk_get_name "$db_name" .psql.gz)" +# [ "$port" ] || port=5432 # Set default PostgreSQL port. +# export PGPASSWORD="$password" +# +# pg_dump \ +# --host="$host" \ +# --port="$port" \ +# --dbname="${path##*/}" \ +# --username="$user" \ +# --no-password | gzip -c > "$dump_name" |& bk_log +# +# unset PGPASSWORD +# +# if [ -s "$dump_name" ]; then +# bk_log "Dumping database '${path##*/}' owned by ${user}@${host} (PostgreSQL) [Done]" +# bk_log "Dump saved as: $dump_name" +# bk_upload_file "$dump_name" +# +# else +# rm "$dump_name" +# bk_err "Something went wrong. Dump size is 0 bytes. Removing $dump_name" +# fi +#} diff --git a/src/lib/handlers/sources/tar.sh b/src/lib/handlers/sources/tar.sh index 283e319..848aeb1 100644 --- a/src/lib/handlers/sources/tar.sh +++ b/src/lib/handlers/sources/tar.sh @@ -26,16 +26,17 @@ handler::tar() { local uri local src_path local dst_path - local opts local archive local exclude local file_ext uri="$1" + # shellcheck disable=SC2154 dst_path="$__main_target_path" parse_uri "$uri" + # shellcheck disable=SC2154 if [ -f "$path" ] || [ -d "$path" ]; then src_path="$path" else @@ -51,15 +52,8 @@ handler::tar() { # Exit if tar is not installed is_installed tar - # Overwrire __tar_options - if [ -n "$tar_options" ]; then - opts="$tar_options" - else - opts="$__tar_options" - fi - - # Overwrite __tar_exclude - if [ "$tar_exclude" ]; then + # Set tar_exclude + if [ -n "$tar_exclude" ]; then for item in "${tar_exclude[@]}"; do exclude+=" --exclude $item" done @@ -67,6 +61,9 @@ handler::tar() { exclude= fi + # Set options + tar_options="${tar_options:--acf}" + # Select filename extension by compression type. if [ "$compression" ]; then # Make sure for the `--auto-compress` is enabled in __tar_options @@ -99,14 +96,14 @@ handler::tar() { [ "$__verbose" ] && { echo "Source path: $src_path" echo "Destination path: $dst_path" - echo "Run command: tar $exclude $opts $archive $src_path" + echo "Run command: tar $exclude $tar_options $archive $src_path" } log "Archiving $src_path to $archive ..." - log "Run command: tar $exclude $opts $archive $src_path" + log "Run command: tar $exclude $tar_options $archive $src_path" # Run tar - try tar "$exclude" "$opts" "$archive" "$src_path" + try tar "$exclude" "$tar_options" "$archive" "$src_path" # Append path to 'backups' array backups+=("$archive") diff --git a/src/lib/handlers/targets/cp.sh b/src/lib/handlers/targets/cp.sh index 9e1768f..d61430a 100644 --- a/src/lib/handlers/targets/cp.sh +++ b/src/lib/handlers/targets/cp.sh @@ -29,6 +29,7 @@ handler::cp() { uri="$1" + # shellcheck disable=SC2154 if [[ "$uri" == "$__main_target" ]]; then : # Do nothing. Source and destination is the same log "Nothing to do: Source and destination is the same: $__main_target" @@ -36,6 +37,7 @@ handler::cp() { # Copy backups to another destination parse_uri "$uri" + # shellcheck disable=SC2154 if [ -f "$path" ] || [ -d "$path" ]; then dst_path="$path" else @@ -46,6 +48,7 @@ handler::cp() { [ "$__verbose" ] && echo "Destination path: $dst_path" # Copy files preserving metadata + # shellcheck disable=SC2154 for backup in "${backups[@]}"; do log "Copying file $backup to $dst_path ..." log "Run command: cp --archive $backup $dst_path" diff --git a/src/lib/lib.sh b/src/lib/lib.sh index 33772d1..f2291e1 100644 --- a/src/lib/lib.sh +++ b/src/lib/lib.sh @@ -1,4 +1,5 @@ #!/usr/bin/env bash +# shellcheck disable=SC1091 # lib.sh - source scripts. # Copyright (c) 2022 ge diff --git a/src/lib/source.sh b/src/lib/source.sh index ea783d9..b06bd0e 100644 --- a/src/lib/source.sh +++ b/src/lib/source.sh @@ -35,7 +35,8 @@ validate_sources() { case "$scheme" in file|mysql|postgres|sqlite) : ;; # do nothing, this is OK - *) echo "Error: $__user_script: Unsupported URI scheme: $scheme" >&2; exit 1;; + *) # shellcheck disable=SC2154 + echo "Error: $__user_script: Unsupported URI scheme: $scheme" >&2; exit 1;; esac done } @@ -78,6 +79,7 @@ validate_targets() { __main_target="${file_targets[0]}" # Fail if __main_target's path is not a directory parse_uri "$__main_target" + # shellcheck disable=SC2154 if [ -d "$path" ]; then __main_target_path="$path" else @@ -99,24 +101,25 @@ source_script() { echo "Error: No such file: $script" >&2; exit 1 fi - # Dry run script, check syntax. See set(1p) + # Dry run script, check syntax. See manpage set(1) if ! bash -n "$script"; then - echo Error: $__user_script: Please check your syntax >&2; exit 1 + echo "Error: $__user_script: Please check your syntax" >&2; exit 1 fi # Source script - . "$@" + # shellcheck disable=SC1090 + . "$script" # Check required variables - if [[ "$sources" ]]; then + if [ -n "$sources" ]; then validate_sources "${sources[@]}" else - echo Error: $__user_script: sources array is not set >&2; exit 1 + echo "Error: $__user_script: sources array is not set" >&2; exit 1 fi - if [[ "$targets" ]]; then + if [ -n "$targets" ]; then validate_targets "${targets[@]}" else - echo Error: $__user_script: targets array is not set >&2; exit 1 + echo "Error: $__user_script: targets array is not set" >&2; exit 1 fi } diff --git a/src/lib/uri.sh b/src/lib/uri.sh index 1367ea0..08fe442 100644 --- a/src/lib/uri.sh +++ b/src/lib/uri.sh @@ -23,7 +23,7 @@ parse_uri() { # URL-encoded passwords supported. # # Usage: parse_uri URI - # Return variables: + # Set variables: # scheme # username # password @@ -113,6 +113,7 @@ parse_uri() { port= fi else + # shellcheck disable=SC2034 hostname="$host" port= fi @@ -122,6 +123,7 @@ parse_uri() { username="${userinfo%:*}" password="${userinfo#*:}" else + # shellcheck disable=SC2034 username="$userinfo" password= fi