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,80 +211,89 @@ 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 Расширение файла Утилита | Умолчание: нет
======================== ================ ========
gzip, gz .tar.gz gzip ==================== ================ ========
tgz .tgz gzip Значение compression Расширение файла Утилита
taz .taz gzip ==================== ================ ========
compress, Z .tar.Z compress gzip, gz .tar.gz gzip
taZ .taZ compress tgz .tgz gzip
bzip2, bz2 .tar.bz2 bzip2 taz .taz gzip
tz2 .tz2 bzip2 compress, Z .tar.Z compress
tbz2 .tbz2 bzip2 taZ .taZ compress
tbz .tbz bzip2 bzip2, bz2 .tar.bz2 bzip2
lzip, lz .tar.lz lzip tz2 .tz2 bzip2
lzma .tar.lzma lzma tbz2 .tbz2 bzip2
tlz .tlz lzma tbz .tbz bzip2
lzop, lzo .tar.lzo lzop lzip, lz .tar.lz lzip
xz .tar.xz xz lzma .tar.lzma lzma
zstd, zst .tar.zst zstd tlz .tlz lzma
tzst .tzst zstd lzop, lzo .tar.lzo lzop
======================== ================ ======== xz .tar.xz xz
zstd, zst .tar.zst 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