feat: Add variable name_suffix; Pass shellcheck; Discard useless variables from main script

This commit is contained in:
ge 2022-06-29 21:27:35 +03:00
parent 3d514678e7
commit aaa7a5c2d3
11 changed files with 177 additions and 147 deletions

View File

@ -23,8 +23,10 @@ tests:
for test in $(tests_dir)/*.bats; do bats --verbose-run --print-output-on-failure "$$test"; done for test in $(tests_dir)/*.bats; do bats --verbose-run --print-output-on-failure "$$test"; done
lint: lint:
shellcheck $(src_dir)/boring-backup || true shellcheck $(src_dir)/boring-backup
shellcheck $(src_dir)/lib/* || true shellcheck $(src_dir)/lib/*.sh
shellcheck $(src_dir)/lib/handlers/sources/*.sh
shellcheck $(src_dir)/lib/handlers/targets/*.sh
docs: builddir docs: builddir
# See rst2man(1), rst2html(1), https://docutils.sourceforge.io/docs/index.html # See rst2man(1), rst2html(1), https://docutils.sourceforge.io/docs/index.html

View File

@ -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. В любом варианте применения утилита требует написания сценария для выполнения бэкапа. boring-backup можно рассматривать как фреймворк или библиотеку для создания сценариев резервного копирования. Сценарии должны содержать валидный код на Bash. Сценарий импортируется в основной скрипт с помощью команды ``source`` (см. ``bash``\(1) п. SHELL BUILTIN COMMANDS).
В сценариях можно использовать переменные и функции. Сценарий импортируется в основной скрипт с помощью команды ``source`` (см. ``bash``\(1) п. SHELL BUILTIN COMMANDS).
Простейший сценарий резервного копирования выглядит следующим образом:: Простейший сценарий резервного копирования выглядит следующим образом::
sources=('file:/home/user') sources=('file:/home/user')
targets=('file:/var/backup') 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 Обзор URI
--------- ---------
@ -101,8 +99,9 @@ Perl 5::
`````````````````````````````````````````````````` ``````````````````````````````````````````````````
file file
Может содержать путь к локальному файлу или директории. Файлы архивируются при помощи ``tar`` и по умолчанию сжимаются с помощью ``gzip``. Примеры:: Может содержать путь к локальному файлу или директории. Файлы архивируются при помощи ``tar`` и по умолчанию не сжимаются. Cжатие архива может быть включено через переменную `compression`. Примеры::
/var/www/www-root/data
file:/var/www/html file:/var/www/html
file:///home/jhon/cool_stuff.txt file:///home/jhon/cool_stuff.txt
@ -115,17 +114,17 @@ mysql
mysql://[username[:password]@]hostname[:port]/database mysql://[username[:password]@]hostname[:port]/database
С помощью утилиты ``mysqldump``\(1) формируется дамп в формате SQL, файл по умолчанию сжимается утилитой ``gzip``. С помощью утилиты ``mysqldump``\(1) формируется дамп в формате SQL. Файл по умолчанию не сжимается, но сжатие может быть включено через переменную `compression`.
См. также ``handler::mysqldump`` См. также ``handler::mysqldump``
postgres postgres
Аналогично `mysql`, но для PostgreSQL. Для создания дампа используется ``pg_dump``\(1), файлы по умолчанию сохраняются с расширением .psql.gz. Аналогично `mysql`, но для PostgreSQL. Для создания дампа используется ``pg_dump``\(1) с расширением .psql. Файл по умолчанию не сжимается, но сжатие может быть включено через переменную `compression`.
См. также ``handler::pg_dump`` См. также ``handler::pg_dump``
sqlite sqlite
Схема для баз данных SQLite. Ничем не отличается от `file`, добавлена для наглядного обозначения, что источником является БД SQLite, а не иной файл. Схема для баз данных SQLite. Мало отличается от `file`, добавлена для наглядного обозначения, что источником является БД SQLite, а не иной файл.
См. также ``handler::sqlite`` См. также ``handler::sqlite``
@ -133,7 +132,7 @@ sqlite
`````````````````````````````````````````````````` ``````````````````````````````````````````````````
file 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``. boring-backup предполагает, что для всех резервных копий необходимо создавать архивы. Поэтому вам нужно следить за тем, чтобы в локальном хранилище всегда хватало дискового пространства для создания новых архивов. boring-backup также не удаляет старые архивы и вам также надо позаботиться об удалении устаревших резервных копий. Изменить это поведение можно с помощью пользовательских функций в сценариях. В этом разделе речь пойдёт о поведении, которое установлено по умолчанию. См. функции ``builtin_backup``, ``process_sources``, ``process_targets``.
Базовая концепция строится на том, что существует несколько точек источников и несколько точек назначения. Создаётся резервная копия источника и копируется в точку назначения. Базовая концепция строится на том, что существует несколько источников и несколько точек назначения. Создаётся резервная копия источника и копируется в точку назначения.
Для всех источников в сценарии выполняется локальная резервная копия. После этого локальная копия переносится в удалённые точки назначения, если они заданы. Копирование локального бэкапа будет осуществлёно столько раз, сколько точек назначения задано. В каждом сценарии обязательно должен быть задан хотя бы один источник и одна точка назначения. При этом в списке точек назначения обязательно должна присутствовать точка со схемой `file`. Именно в неё будут сохранены архивы. Для всех источников в сценарии выполняется локальная резервная копия. После этого локальная копия переносится в удалённые точки назначения, если они заданы. Копирование локального бэкапа будет осуществлёно столько раз, сколько точек назначения задано. В каждом сценарии обязательно должен быть задан хотя бы один источник и одна точка назначения. При этом в списке точек назначения обязательно должна присутствовать точка со схемой `file`. Именно в неё будут сохранены архивы.
Пример. Имеется следующий сценарий:: Например, имеется следующий сценарий::
sources=( sources=(
'mysql://wordpress:1jobrRRjtLYs@localhost/wordpress' 'mysql://wordpress:1jobrRRjtLYs@localhost/wordpress'
@ -212,63 +211,72 @@ WebDAV
``backups`` ``backups``
Массив со списком созданных бэкапов. Содержит абсолютные пути к файлам. Заполняется функциями-обработчиками `sources`. Массив со списком созданных бэкапов. Содержит абсолютные пути к файлам. Заполняется функциями-обработчиками `sources`.
Тип: массив | Тип: массив
Умолчание: () | Умолчание: ()
``errors`` ``errors``
Массив с текстами ошибок. Заполняется функцией ``err`` с опцией ``-a``. Массив с текстами ошибок. Заполняется функцией ``err`` с опцией ``-a``.
Тип: массив | Тип: массив
Умолчание: () | Умолчание: ()
``log_date_fmt`` ``log_date_fmt``
Формат даты в логе. См. ``date``\(1). Формат даты в логе. См. ``date``\(1).
Тип: строка | Тип: строка
Умолчание: %d/%b/%Y:%H:%M:%S %z | Умолчание: %d/%b/%Y:%H:%M:%S %z
``name_prefix`` ``name_prefix``
Префикс имени файла. Может быть задан в скрипте. Префикс имени файла. Может быть задан в скрипте.
Тип: строка | Тип: строка
Умолчание: имя_скрипта\_ | Умолчание: имя_скрипта\_
``name`` ``name``
Соответствует имени архивируемой директории/файла полученного из `path` с помощью утилиты ``basename``\(1). Соответствует имени архивируемой директории/файла полученного из `path` с помощью утилиты ``basename``\(1).
Тип: строка | Тип: строка
Умолчание: нет | Умолчание: нет
``name_date_fmt`` ``name_date_fmt``
Дата, интерпретируемая утилитой ``date``\(1). Дата, интерпретируемая утилитой ``date``\(1).
Тип: строка | Тип: строка
Умолчание: _%Y%m%d-%H%M | Умолчание: _%Y%m%d
``name_suffix``
Суффикс, добавляемый к имени файла перед расширением. Строка интерпретируется утилитой ``date``.
| Тип: строка
| Умолчание: -%H%M
``name_ext`` ``name_ext``
Расширение имени файла, соответствующее формату архива. Расширение имени файла, соответствующее формату архива.
Тип: строка | Тип: строка
Умолчание: .tar.gz | Умолчание: .tar.gz
``tar_options`` ``tar_options``
Опции ``tar``. См. ``tar``\(1). Опции ``tar``. См. ``tar``\(1).
Тип: строка | Тип: строка
Умолчание: -acf | Умолчание: -acf
``tar_exclude`` ``tar_exclude``
Список имён для опции ``tar`` ``--exclude``. Список имён для опции ``tar`` ``--exclude``.
Тип: массив | Тип: массив
Умолчание: нет | Умолчание: нет
``compression`` ``compression``
В контексте ``tar`` работа этой фичи базируется на опции ``tar`` ``--auto-compress``. Переменная может принимать значения, в соответствии с табилцей ниже. Если переменная имеет значение, отличное от перечисленныых, то будет использован ``gzip``. В контексте ``tar`` работа этой фичи базируется на опции ``tar`` ``--auto-compress``. Переменная может принимать значения, в соответствии с табилцей ниже. Если переменная имеет значение, отличное от перечисленныых, то будет использован ``gzip``. Если переменная пуста или не задана, то сжатие будет отключено.
======================== ================ ======== | Тип: строка
Значение tar_compression Расширение файла Утилита | Умолчание: нет
======================== ================ ========
==================== ================ ========
Значение compression Расширение файла Утилита
==================== ================ ========
gzip, gz .tar.gz gzip gzip, gz .tar.gz gzip
tgz .tgz gzip tgz .tgz gzip
taz .taz gzip taz .taz gzip
@ -285,7 +293,7 @@ WebDAV
xz .tar.xz xz xz .tar.xz xz
zstd, zst .tar.zst zstd zstd, zst .tar.zst zstd
tzst .tzst zstd tzst .tzst zstd
======================== ================ ======== ==================== ================ ========
Функции Функции
------- -------

View File

@ -20,21 +20,17 @@ __version='0.0.0'
__config= __config=
__verbose= __verbose=
__log_file='./log.txt' __log_file='./log.txt'
__log_date_fmt='%d/%b/%Y:%H:%M:%S %z'
__pid_file='/tmp/boring-backup.pid' __pid_file='/tmp/boring-backup.pid'
__tar_options='-acf'
__tar_exclude=
__name_date_fmt='_%Y%m%d-%H%M'
__compression=
__errors_count=0 __errors_count=0
__errors_file='/tmp/boring-backup.errors' __errors_file='/tmp/boring-backup.errors'
echo $__errors_count > $__errors_file echo "$__errors_count" > "$__errors_file"
LIBRARY="${LIBRARY:-./lib}" LIBRARY="${LIBRARY:-./lib}"
# Source library # Source library
if [ -f "$LIBRARY/lib.sh" ]; then if [ -f "$LIBRARY/lib.sh" ]; then
# shellcheck disable=SC1091
. "$LIBRARY/lib.sh" . "$LIBRARY/lib.sh"
else else
echo "Error: Cannot source library from $LIBRARY" >&2 echo "Error: Cannot source library from $LIBRARY" >&2
@ -69,7 +65,7 @@ optval() {
# #
# Parse --opt VAL and --opt=VAL options. # Parse --opt VAL and --opt=VAL options.
# Requires 2 arguments: $1, $2. # Requires 2 arguments: $1, $2.
# Return variables: # Set variables:
# opt option name. # opt option name.
# val option value. # val option value.
# sft value for shift. # sft value for shift.
@ -96,7 +92,8 @@ for args in "$@"; do
set -- "$@" "$args";; # save long options set -- "$@" "$args";; # save long options
-*) -*)
args="$(echo "${args:1}" | grep -o . | xargs -I {} echo -n '-{} ')" 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 set -- "$@" "$args";; # save positional arguments
esac esac
@ -123,20 +120,22 @@ done
log "Backup STARTED" log "Backup STARTED"
# Check PID file # Check PID file
if [ -e $__pid_file ]; then if [ -e "$__pid_file" ]; then
if [ -z "$(ps ax -o pid | grep "$(cat $__pid_file)")" ]; then # shellcheck disable=SC2009
err "Process $(cat $__pid_file) died." # shellcheck disable=SC2143
rm $__pid_file if [ -z "$(ps ax -o pid | grep "$(cat "$__pid_file")")" ]; then
err "Process $(cat "$__pid_file") died."
rm "$__pid_file"
else else
err -e "Process $(cat $__pid_file) still running."; err -e "Process $(cat "$__pid_file") still running.";
fi fi
fi fi
# Touch PID file # Touch PID file
echo $$ > $__pid_file echo "$$" > "$__pid_file"
# Scripts counter. # Scripts counter.
__count=${#__args[@]} # count __count="${#__args[@]}" # count
__iter=1 # iterator __iter=1 # iterator
# Startup log. # Startup log.
@ -144,18 +143,22 @@ date +'Start: %d %b %Y %T %z'
log -p "Library path: $LIBRARY" log -p "Library path: $LIBRARY"
log -p "Log file: $__log_file" log -p "Log file: $__log_file"
log -p "Configuration file: $([ "$__config" ] || echo not specified && echo "$__config")" 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 for script in "${__args[@]}"; do
# Initialise variables # Initialise variables
__user_script="$script" __user_script="$script"
# shellcheck disable=SC2034
backups=() # Array of created backups, contains full pathes backups=() # Array of created backups, contains full pathes
# shellcheck disable=SC2034
errors=() # Array of error messages written by err() function errors=() # Array of error messages written by err() function
# Source scripts # Source scripts
source_script "$script" source_script "$script"
# Config can ovewrite script functions and variables # Config can ovewrite script functions and variables
# shellcheck source=/dev/null
[ -n "$__config" ] && . "$__config" [ -n "$__config" ] && . "$__config"
echo echo
@ -209,7 +212,7 @@ if [[ "$(cat $__errors_file)" != '0' ]]; then
echo echo
log -p "Backup [Failed]: Process finished with $(cat $__errors_file) errors."\ log -p "Backup [Failed]: Process finished with $(cat $__errors_file) errors."\
"See $__log_file for info." "See $__log_file for info."
rm $__errors_file rm "$__errors_file"
else else
echo -e "\nBackup [Done]" echo -e "\nBackup [Done]"
fi fi
@ -217,4 +220,4 @@ fi
log "Backup FINISHED" log "Backup FINISHED"
# Remove PID file # Remove PID file
rm $__pid_file rm "$__pid_file"

View File

@ -37,7 +37,9 @@ process_source() {
mysql) handler='handler::mysqldump';; mysql) handler='handler::mysqldump';;
postgres) handler='handler::pg_dump';; postgres) handler='handler::pg_dump';;
sqlite) handler='handler::sqlite';; 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 esac
# Run handler function # Run handler function
@ -84,10 +86,12 @@ builtin_backup() {
# #
# Usage: builtin_backup # Usage: builtin_backup
# shellcheck disable=SC2154
for source in "${sources[@]}"; do for source in "${sources[@]}"; do
process_source "$source" process_source "$source"
done done
# shellcheck disable=SC2154
for target in "${targets[@]}"; do for target in "${targets[@]}"; do
process_target "$target" process_target "$target"
done done
@ -102,21 +106,15 @@ gen_backup_name() {
# #
# Usage: gen_backup_name NAME_EXT # Usage: gen_backup_name NAME_EXT
local prefix
local name local name
local date_fmt
local name_ext local name_ext
[ -n "$name_prefix" ] || { prefix="${__user_script}_"; } name_prefix="${name_prefix:-${__user_script}_}"
name="$(basename $path)" # 'path' is variable parsed from URI # shellcheck disable=SC2154
name="$(basename "$path")" # 'path' is variable parsed from URI
name_ext="$1" name_ext="$1"
name_date_fmt="${name_date_fmt:-_%Y.%m.%d}"
name_suffix="${name_suffix:--%H%M}"
# Overwrite __name_date_fmt date +"${name_prefix}${name}${name_date_fmt}${name_suffix}${name_ext}"
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}"
} }

View File

@ -31,7 +31,7 @@ log() {
[[ ! -t 0 ]] || [[ "$#" == 0 ]] && message="$(cat <&0)" [[ ! -t 0 ]] || [[ "$#" == 0 ]] && message="$(cat <&0)"
# Set log date format # 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 while (( "$#" )); do
case "$1" in case "$1" in
@ -44,7 +44,8 @@ log() {
[[ "$print" == 1 ]] && echo -e "$message" [[ "$print" == 1 ]] && echo -e "$message"
while read -r line; do 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" printf '[%s] %s\n' "$(date +"$log_date_fmt")" "$line" >> "$__log_file"
fi fi
done <<< "$(sed -r 's/\x1B\[(([0-9]+)(;[0-9]+)*)?[m,K,H,f,J]//g' <<< "$message")" done <<< "$(sed -r 's/\x1B\[(([0-9]+)(;[0-9]+)*)?[m,K,H,f,J]//g' <<< "$message")"
@ -69,7 +70,8 @@ err() {
case "$args" in case "$args" in
-*) -*)
args="$(echo "${args:1}" | grep -o . | xargs -I {} echo -n '-{} ')" 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 set -- "$@" "$args";; # save positional arguments
esac esac
@ -100,9 +102,10 @@ err() {
# Exit # Exit
if [ "$errexit" ]; then if [ "$errexit" ]; then
# Count errors for backup recap # Count errors for backup recap
__errors_count="$(cat $__errors_file)" # shellcheck disable=SC2154
__errors_count="$(cat "$__errors_file")"
((__errors_count++)) || true ((__errors_count++)) || true
echo $__errors_count > "$__errors_file" echo "$__errors_count" > "$__errors_file"
log -p "Backup ERROR: Exiting with previous errors" >&2; exit 1 log -p "Backup ERROR: Exiting with previous errors" >&2; exit 1
fi fi
@ -113,7 +116,8 @@ try() {
# #
# Usage: try COMMAND # Usage: try COMMAND
if eval "$@" 2>> "$__log_file"; then # https://www.shellcheck.net/wiki/SC2294
if "$@" 2>> "$__log_file"; then
return 0 # Success return 0 # Success
else else
# Count errors # Count errors
@ -168,6 +172,11 @@ set_compression() {
xz) file_ext='.xz'; compr_util='xz';; xz) file_ext='.xz'; compr_util='xz';;
zstd|zst) file_ext='.zst'; compr_util='zstd';; zstd|zst) file_ext='.zst'; compr_util='zstd';;
tzst) 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 esac
} }

View File

@ -32,12 +32,14 @@ handler::mysqldump() {
local file_ext local file_ext
uri="$1" uri="$1"
# shellcheck disable=SC2154
dst_path="$__main_target_path" dst_path="$__main_target_path"
parse_uri "$uri" parse_uri "$uri"
[ -z "$port" ] && port=3306 # Set default MySQL port. [ -z "$port" ] && port=3306 # Set default MySQL port.
# shellcheck disable=SC2154
[ -n "$__verbose" ] && echo "Dumping database '${path##*/}'"\ [ -n "$__verbose" ] && echo "Dumping database '${path##*/}'"\
"owned by ${username}@${hostname} ..." | log -p "owned by ${username}@${hostname} ..." | log -p
@ -61,7 +63,10 @@ handler::mysqldump() {
compr_cmd="cat" compr_cmd="cat"
fi fi
mysqldump_opts="${mysqldump_opts:-}"
# NOTE! mysqldump_opts and compr_cmd variables must be unquoted! # NOTE! mysqldump_opts and compr_cmd variables must be unquoted!
# shellcheck disable=SC2086,SC2154
try mysqldump $mysqldump_opts \ try mysqldump $mysqldump_opts \
--host="$hostname" \ --host="$hostname" \
--port="$port" \ --port="$port" \
@ -76,33 +81,32 @@ handler::mysqldump() {
fi fi
} }
# POSTGRESQL vvv # POSTGRESQL
#bk_dump_postgresql() {
bk_dump_postgresql() { # # Do PostgreSQL dump.
# Do PostgreSQL dump. #
# bk_log "Dumping database '${path##*/}' owned by ${user}@${host} (PostgreSQL) ..."
bk_log "Dumping database '${path##*/}' owned by ${user}@${host} (PostgreSQL) ..." #
# dump_name="$entry_local"/"$(bk_get_name "$db_name" .psql.gz)"
dump_name="$entry_local"/"$(bk_get_name "$db_name" .psql.gz)" # [ "$port" ] || port=5432 # Set default PostgreSQL port.
[ "$port" ] || port=5432 # Set default PostgreSQL port. # export PGPASSWORD="$password"
export PGPASSWORD="$password" #
# pg_dump \
pg_dump \ # --host="$host" \
--host="$host" \ # --port="$port" \
--port="$port" \ # --dbname="${path##*/}" \
--dbname="${path##*/}" \ # --username="$user" \
--username="$user" \ # --no-password | gzip -c > "$dump_name" |& bk_log
--no-password | gzip -c > "$dump_name" |& bk_log #
# unset PGPASSWORD
unset PGPASSWORD #
# if [ -s "$dump_name" ]; then
if [ -s "$dump_name" ]; then # bk_log "Dumping database '${path##*/}' owned by ${user}@${host} (PostgreSQL) [Done]"
bk_log "Dumping database '${path##*/}' owned by ${user}@${host} (PostgreSQL) [Done]" # bk_log "Dump saved as: $dump_name"
bk_log "Dump saved as: $dump_name" # bk_upload_file "$dump_name"
bk_upload_file "$dump_name" #
# else
else # rm "$dump_name"
rm "$dump_name" # bk_err "Something went wrong. Dump size is 0 bytes. Removing $dump_name"
bk_err "Something went wrong. Dump size is 0 bytes. Removing $dump_name" # fi
fi #}
}

View File

@ -26,16 +26,17 @@ handler::tar() {
local uri local uri
local src_path local src_path
local dst_path local dst_path
local opts
local archive local archive
local exclude local exclude
local file_ext local file_ext
uri="$1" uri="$1"
# shellcheck disable=SC2154
dst_path="$__main_target_path" dst_path="$__main_target_path"
parse_uri "$uri" parse_uri "$uri"
# shellcheck disable=SC2154
if [ -f "$path" ] || [ -d "$path" ]; then if [ -f "$path" ] || [ -d "$path" ]; then
src_path="$path" src_path="$path"
else else
@ -51,15 +52,8 @@ handler::tar() {
# Exit if tar is not installed # Exit if tar is not installed
is_installed tar is_installed tar
# Overwrire __tar_options # Set tar_exclude
if [ -n "$tar_options" ]; then if [ -n "$tar_exclude" ]; then
opts="$tar_options"
else
opts="$__tar_options"
fi
# Overwrite __tar_exclude
if [ "$tar_exclude" ]; then
for item in "${tar_exclude[@]}"; do for item in "${tar_exclude[@]}"; do
exclude+=" --exclude $item" exclude+=" --exclude $item"
done done
@ -67,6 +61,9 @@ handler::tar() {
exclude= exclude=
fi fi
# Set options
tar_options="${tar_options:--acf}"
# Select filename extension by compression type. # Select filename extension by compression type.
if [ "$compression" ]; then if [ "$compression" ]; then
# Make sure for the `--auto-compress` is enabled in __tar_options # Make sure for the `--auto-compress` is enabled in __tar_options
@ -99,14 +96,14 @@ handler::tar() {
[ "$__verbose" ] && { [ "$__verbose" ] && {
echo "Source path: $src_path" echo "Source path: $src_path"
echo "Destination path: $dst_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 "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 # Run tar
try tar "$exclude" "$opts" "$archive" "$src_path" try tar "$exclude" "$tar_options" "$archive" "$src_path"
# Append path to 'backups' array # Append path to 'backups' array
backups+=("$archive") backups+=("$archive")

View File

@ -29,6 +29,7 @@ handler::cp() {
uri="$1" uri="$1"
# shellcheck disable=SC2154
if [[ "$uri" == "$__main_target" ]]; then if [[ "$uri" == "$__main_target" ]]; then
: # Do nothing. Source and destination is the same : # Do nothing. Source and destination is the same
log "Nothing to do: Source and destination is the same: $__main_target" log "Nothing to do: Source and destination is the same: $__main_target"
@ -36,6 +37,7 @@ handler::cp() {
# Copy backups to another destination # Copy backups to another destination
parse_uri "$uri" parse_uri "$uri"
# shellcheck disable=SC2154
if [ -f "$path" ] || [ -d "$path" ]; then if [ -f "$path" ] || [ -d "$path" ]; then
dst_path="$path" dst_path="$path"
else else
@ -46,6 +48,7 @@ handler::cp() {
[ "$__verbose" ] && echo "Destination path: $dst_path" [ "$__verbose" ] && echo "Destination path: $dst_path"
# Copy files preserving metadata # Copy files preserving metadata
# shellcheck disable=SC2154
for backup in "${backups[@]}"; do for backup in "${backups[@]}"; do
log "Copying file $backup to $dst_path ..." log "Copying file $backup to $dst_path ..."
log "Run command: cp --archive $backup $dst_path" log "Run command: cp --archive $backup $dst_path"

View File

@ -1,4 +1,5 @@
#!/usr/bin/env bash #!/usr/bin/env bash
# shellcheck disable=SC1091
# lib.sh - source scripts. # lib.sh - source scripts.
# Copyright (c) 2022 ge <https://nixhacks.net/> # Copyright (c) 2022 ge <https://nixhacks.net/>

View File

@ -35,7 +35,8 @@ validate_sources() {
case "$scheme" in case "$scheme" in
file|mysql|postgres|sqlite) : ;; # do nothing, this is OK 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 esac
done done
} }
@ -78,6 +79,7 @@ validate_targets() {
__main_target="${file_targets[0]}" __main_target="${file_targets[0]}"
# Fail if __main_target's path is not a directory # Fail if __main_target's path is not a directory
parse_uri "$__main_target" parse_uri "$__main_target"
# shellcheck disable=SC2154
if [ -d "$path" ]; then if [ -d "$path" ]; then
__main_target_path="$path" __main_target_path="$path"
else else
@ -99,24 +101,25 @@ source_script() {
echo "Error: No such file: $script" >&2; exit 1 echo "Error: No such file: $script" >&2; exit 1
fi fi
# Dry run script, check syntax. See set(1p) # Dry run script, check syntax. See manpage set(1)
if ! bash -n "$script"; then 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 fi
# Source script # Source script
. "$@" # shellcheck disable=SC1090
. "$script"
# Check required variables # Check required variables
if [[ "$sources" ]]; then if [ -n "$sources" ]; then
validate_sources "${sources[@]}" validate_sources "${sources[@]}"
else 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 fi
if [[ "$targets" ]]; then if [ -n "$targets" ]; then
validate_targets "${targets[@]}" validate_targets "${targets[@]}"
else 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 fi
} }

View File

@ -23,7 +23,7 @@ parse_uri() {
# URL-encoded passwords supported. # URL-encoded passwords supported.
# #
# Usage: parse_uri URI # Usage: parse_uri URI
# Return variables: # Set variables:
# scheme # scheme
# username # username
# password # password
@ -113,6 +113,7 @@ parse_uri() {
port= port=
fi fi
else else
# shellcheck disable=SC2034
hostname="$host" hostname="$host"
port= port=
fi fi
@ -122,6 +123,7 @@ parse_uri() {
username="${userinfo%:*}" username="${userinfo%:*}"
password="${userinfo#*:}" password="${userinfo#*:}"
else else
# shellcheck disable=SC2034
username="$userinfo" username="$userinfo"
password= password=
fi fi