Содержание
BackUp и синхронизация
За долгую деятельность в области системного администрирования было написано много bash-скриптов для различных «мелких» задач. Некоторяе скрипты запускались по cron-ну или incron-ну, а некоторые через алиасы.
Часть скриптов по сути выполняли одинаковую задачу, но с разными данными. Например, создание dd-образов разделов жесткого диска. Для объединения скриптов, запускаемых в ручном режиме, с одинаковыми задачами и были написаны данные bash-скрипты.
Все скрипты рабочие. Т.е. регулярно используются реально. Много раз переписывались. Есть задумки их «расширений» и т.д.
Скрипты объединены в две независимые группы. Условно группы названы «BackUpData» и «SyncData».
«BackUpData» и «SyncData» написаны по одинаковым принципам:
- Модульность. Подключение дополнительныз скриптов с минимальными изменениями кода.
- Построение динамического древовидного интерактивного меню в зависимости от подключаемых скриптов, текущего компьютера и других условий.
- Широкое использование функций «главного» скрипта в подключаемых скриптах.
- Хранение данных отдельно от кода. Правда, это относится к подключаемым скриптам. В них реализована очень гибкая возможность выполнения действий в зависимоти от разных условий (см. «.arrays»-файлы подключаемых скриптов).
Описывать буду на основе скрипта «BackUpData». Для «SyncData» опишу только принципиальные отличия.
BackUpData
Назначение этого «главного» скрипта:
- Подключение дополнительных скриптов
- Работа с древовидным меню с возможность возврата к предыдущему пункту меню.
- Обеспечить подключаемые скрипты различными функциями, связанными с задачами backup-а.
- Вызов действий пунктов меню (через вызов приписанных функций находящихся в подключаемых скриптах).
- Обеспечение динамического изменения пунктов меню в зависимости от результата выполнения пункта меню.
- Обеспечение показа информационных сообщений и сообщений возникновения ошибок работы скриптов.
Вот пример меню при вызове скрипта с параметром «BackUpData.bash debug_menu_main»:
Вот пример меню при вызове скрипта с параметром «BackUpData.bash debug_menu_main»:
##### menu_main (): 1: Создание архивных копий файлов (всего файлов: 5)=show_menu 1.1: файл: "asus-x55c.tc" (8,1G)=show_menu 1.1.1: создать архив в "media/VtData/BackUp/Data/HD/Work/TC" (свободно: 344G) (последний: 2022-06-02)=backup_file_run asus-x55c.tc asus-x55c.tc:VtData 1.1.2: создать архив в "media/wd1t/BackUp/Data/HD/Work/TC" (свободно: 221G) (последний: 2022-06-02)=backup_file_run asus-x55c.tc asus-x55c.tc:wd1t 1.2: файл: "w7-64.qcow2" (12G)=show_menu 1.2.1: создать архив в "media/wd1t/BackUp/Virt-Storage/asus-x55c/AQEMU" (свободно: 221G) (последний: 2022-06-02)=backup_file_run w7-64.qcow2 w7-64.qcow2:wd1t 1.3: файл: "w7-RDPWrap.qcow2" (9,8G)=show_menu 1.3.1: создать архив в "media/wd1t/BackUp/Virt-Storage/asus-x55c/AQEMU" (свободно: 221G) (последний: 2022-01-18)=backup_file_run w7-RDPWrap.qcow2 w7-RDPWrap.qcow2:wd1t 2: Создание образов внутренних дисков=show_menu 2.1: Диск "backup" не подключен=empty 2.2: Создание образов внутренних дисков на "media/wd1t" (свободно: 220,9G)=show_menu 2.2.1: создать образ "sda1" (300M) на "media/wd1t" (последний: 2022-06-02)/}=dd_disk_run wd1t wd1t:sda1 2.2.2: создать образ "sda3" (20G) на "media/wd1t" (последний: 2022-06-02)/}=dd_disk_run wd1t wd1t:sda3 2.2.3: создать образ "sda4" (18,6G) на "media/wd1t" (последний: 2022-06-02)/}=dd_disk_run wd1t wd1t:sda4 3: Создание образов флешек (всего флешек: 2)=show_menu 3.1: Создание ".dd_gzip"-образа флешки "Debian_11_64_live" (28,5G)=show_menu 3.1.1: создать образ (последний: 2022-06-03) на "media/VtData" (свободно: 343,4G)=dd_flash_run Debian_11_64_live Debian_11_64_live:VtData VtData 3.1.2: создать образ (последний: 2022-06-03) на "media/wd1t" (свободно: 220,9G)=dd_flash_run Debian_11_64_live Debian_11_64_live:wd1t wd1t 3.2: Флешка: "Nik-rp" не подключена=empty "debug_menu_main" "Enter" - продолжение, "Ctrl+c" - прервать
А так это выводится на консоль (примеры):
Создание образов внутренних дисков на «media/wd1t» (свободно: 220,9G)
Создание образов внутренних дисков на «media/wd1t» (свободно: 220,9G)
Создание «.dd_gzip»-образа флешки «Debian_11_64_live» (28,5G)
Создание «.dd_gzip»-образа флешки «Debian_11_64_live» (28,5G)
Скрипт BackUpData
BackUpData.bash
BackUpData.bash
#!/bin/bash
# set -e
# set -x
################
# Главный скрипт
################
# Обрабатывает параметры:
# debug_menu_main - показ массива главного меню
# lsblk_show - показ блочных устройств
clear
#===================
# Начальные проверки
#===================
if [ -z "$(command -v whiptail)" ]; then
echo "Не установлен пакет \"whiptail\"!"
exit 0
fi
# Это должно быть в самом начале!!!
name_scr=${0##*/}
name_scr=${name_scr%.*}
readonly name_scr
readonly title_scr="BackUp данных на \"${HOSTNAME}\" (${name_scr})"
readonly IFS_OLD=$IFS
# pgrep не понимает имен более 15 символов!
if [ $(pgrep -c "${name_scr:0:15}") -gt 1 ]; then
whiptail --title "${name_scr}" --msgbox "Скрипт уже запущен!" 7 50
exit
fi
# Должна быть первой функцией
function whiptail_msgbox {
[[ -z "$@" ]] && return
local strings="$@"
local lines=$(echo -e "${strings}" | wc -l)
local height=7
local height_max=12
[[ "${lines}" -gt "1" ]] && height=$((height + ${lines}))
local width=60
local width_max=80
IFS=$'\n'
for string in $(echo -e ${strings}); do
[[ "${#string}" -gt "${width}" ]] && width=${#string}
done
IFS=${IFS_OLD}
[[ "${width}" -gt "${width_max}" ]] && width=${width_max}
if [ "${height}" -gt "${height_max}" ]; then
whiptail --scrolltext --title "${title_scr}" --msgbox "${strings}" ${height_max} ${width}
else
whiptail --title "${title_scr}" --msgbox "${strings}" ${height} ${width}
fi
}
#================
# Общие константы
#================
# pv
readonly PvInstalled="$(command -v pv)"
# netcat (nc)
readonly NetcatInstalled="$(command -v netcat)"
# Каталог откуда запущен скрипт
readonly ScriptDir="$(cd -- "$(dirname -- "${0}")" &> /dev/null && pwd)"
# Каталог дополнительных подключаемых скриптов
readonly Inclusions="${ScriptDir}/Inclusions"
if [ ! -d ${Inclusions} ]; then
whiptail_msgbox "Не найден каталог:\n${Inclusions}"
exit
fi
# Каталог дополнительных файлов для подключаемых скриптов
readonly ScriptCfg="${ScriptDir}/${name_scr}Cfg"
if [ ! -d ${ScriptCfg} ]; then
whiptail_msgbox "Не найден каталог:\n${ScriptCfg}"
exit
fi
# Шаблон
readonly template='????-??-??'
#==============
# Общие функции
#==============
function press_enter {
# Вывод сообщений в консоль и ожидание нажатия Enter
# использовать echo здесь не получится
# из-за возврата результата через echo в некоторых вызывающих функциях!
set +x
if [ -z "$@" ]; then
read -p "\"Enter\" - продолжение, \"Ctrl+c\" - прервать"
else
read -p "\"$@\" \"Enter\" - продолжение, \"Ctrl+c\" - прервать"
fi
}
function arr_keys_sort {
# Возвращает отсортированную строку ключей массива
# "$1" - имя массива
[[ -z "$1" ]] && return
declare -n arr_keys_declare="$1"
if [[ ${#arr_keys_declare[@]} == 0 ]]; then
press_enter "$FUNCNAME: Массив \"$1\" не существует или пуст!"
return
fi
local sorted_arr_keys
IFS=$'\n'
sorted_arr_keys=($(sort <<< "${!arr_keys_declare[*]}"))
IFS=${IFS_OLD}
echo "${sorted_arr_keys[@]}"
}
function debug_menu_main {
# Показ массива главного меню для отладки
# Можно вставлять для отладки в разные места и передавать параметр $@
# Например передавать $FUNCNAME для отображения имени вызывающей функции
set +x
clear
echo -e "\n##### menu_main ($@):"
for key in $(arr_keys_sort "main_menu"); do
echo "${key}: ${main_menu[${key}]}"
done
press_enter "$FUNCNAME"
}
function debug_menu_current {
# Показ массива текущего меню для отладки
# Можно вставлять для отладки в разные места и передавать параметр $@
# Например передавать $FUNCNAME для отображения имени вызывающей функции
set +x
echo -e "\n##### menu_current ($@):"
for element in "${menu_current[@]}"; do
echo "${element}"
done
press_enter "$FUNCNAME"
}
function replacement {
[[ -z "$1" ]] && return
local txt="$1"
txt="${txt//${HOME}/'$HOME'}"
txt="${txt//\/media\/${USER}/media}"
# Обрезание строки
local max_length=40
[[ -n "$2" ]] && max_length="$2"
if [ ${#txt} -gt $((max_length + 4)) ]; then
txt="${txt:0:$((max_length / 2))}...${txt:(-$((max_length / 2)))}"
fi
echo "${txt}"
}
function vars_declare_local {
# Для создания локальных переменных чеsрез echo в вызывающей функции
# "$@" - имена массивов откуда брать переменные
[[ -z "$@" ]] && return
local result=''
for name_vars_declare in "$@"; do
declare -n arr_vars_declare="${name_vars_declare}"
if [[ ${#arr_vars_declare[@]} == 0 ]]; then
press_enter "$FUNCNAME: Массив \"${name_vars_declare}\" не существует или пуст!"
continue
fi
for var_vars_declare in ${arr_vars_declare[@]}; do
[[ -n "${result}" ]] && result+=' '
result+='local '${var_vars_declare}
done
done
echo -e "${result}"
}
function vars_init {
# Установка значений поумолчанию записанных в массивах
# Меняет переменные вызывающей функции!!!
# Применять после vars_declare_local или другого объявления локальных переменных в вызывающей функции!
# "$@" - имена массивов откуда брать переменные
[[ -z "$@" ]] && return
for name_vars_init in "$@"; do
declare -n arr_vars_init="${name_vars_init}"
if [[ ${#arr_vars_init[@]} == 0 ]]; then
echo -e "\nМассив \"${name_vars_init}\" не существует или пуст!"
press_enter "$FUNCNAME"
continue
fi
for var_vars_init in ${arr_vars_init[@]}; do
if [[ ${var_vars_init} == *'='* ]]; then eval ${var_vars_init}; else eval ${var_vars_init}=''; fi
done
done
}
function vars_set {
# Устанавливает актуальные значения переменных
# Меняет значения переменных в вызывающей функции!!!
# "$@" - элементы массивов откуда брать значения переменных
[[ -z "$@" ]] && return
local element_vars_set
for name_vars_set in "$@"; do
element_vars_set="${!name_vars_set}"
if [[ ${#element_vars_set[@]} == 0 ]]; then
echo -e "\nМассив \"${name_vars_set}\" не существует или пуст!"
press_enter "$FUNCNAME"
continue
fi
for var in "${element_vars_set[@]}"; do [[ ${var} == *'='* ]] && eval ${var}; done
done
}
function vars_test {
# Вывод пременных и их значений для тестирования
# Не применять если вызывающая функция возвращает что-то через echo!!!
# "$@" - имена массивов откуда брать переменные
[[ -z "$@" ]] && return
for name_vars_test in "$@"; do
echo -e "\nмассив переменных: ${name_vars_test}"
declare -n arr_vars_test="${name_vars_test}"
if [[ ${#arr_vars_test[@]} == 0 ]]; then
echo -e "\nМассив \"${name_vars_test}\" не существует или пуст!"
press_enter "$FUNCNAME"
continue
fi
for var_vars_test in ${arr_vars_test[@]}; do
[[ ${var_vars_test} == *'='* ]] && var_vars_test=${var_vars_test//'='*/''}
echo "${var_vars_test}:${!var_vars_test}"
done
done
press_enter "$FUNCNAME"
}
function archiver_file_ext {
# Возвращает дополнительное расширение для файла
# $@ - имя архиватора
[[ -z "$@" ]] && return
local extension
if [[ -n "$(command -v $@)" ]]; then
case "$@" in
gzip)
extension=".gz"
;;
tar)
extension="tar.gz"
;;
7z)
extension="7z"
;;
esac
fi
echo "${extension}"
}
function archiver_dd_ext {
# Возвращает дополнительное расширение для dd-файла
# $@ - имя архиватора
local return=".dd"
[[ -n "$@" ]] && [[ -n "$(command -v $@)" ]] && return=".dd_$@"
echo "${return}"
}
function archiver_params {
# Возвращает архиватор с дополнительными параметрами (если нужны)
# $@ - имя архиватора
[[ -z "$@" ]] && return
[[ -z "$(command -v $@)" ]] && return
local params="$@"
case "$@" in
gzip)
params="gzip"
;;
tar)
params='tar --exclude=lost+found --exclude=.Trash-* -zcvf'
;;
7z)
params='7z -xr!lost+found -xr!.Trash-* a'
;;
esac
echo "${params}"
}
function lsblk_show {
# дописать!
local heade=''
echo -e "$(lsblk -o NAME,PTUUID,PARTUUID,UUID,PTTYPE,TYPE,LABEL,SIZE,MODEL,VENDOR,SERIAL,HOTPLUG)"
# for string in "$(lsblk -o NAME,PTUUID,PARTUUID,UUID,PTTYPE,TYPE,SIZE,MOUNTPOINT,HOTPLUG)"; do
# # if [ -z "${heade}" ]; then
# # heade=$(echo ${string})
# # echo "${heade}"
# # continue
# # fi
# echo -e "${string}"
# done
press_enter
}
function disk_name {
# Возвращает имя диска
# "$@" - id или uuid диска
# переписать через lsblk ?
[[ -z "$@" ]] && return
local disk_name="$(ls -l /dev/disk/by-uuid/ | grep -w "$@" | awk '{print $11}')"
[[ -z "${disk_name}" ]] && disk_name="$(ls -l /dev/disk/by-id/ | grep -w "$@" | head -n 1 | awk '{print $11}')"
disk_name=${disk_name//..\//''}
echo "${disk_name}"
}
function disk_parent_name {
# Возвращает имя "родительского" диска
# "$@" - имя диска
[[ -z "$@" ]] && return
local disk_name="$@"
local var="$(lsblk -n -o TYPE /dev/${disk_name} 2> /dev/null)"
if [ -z "${var}" ]; then
echo -e "Ошибка параметра: \"$@\"!"
press_enter "$FUNCNAME"
return
fi
if [ "${var}" == 'part' ]; then
disk_name=''
var=$(lsblk -n -o PTUUID /dev/$@)
[[ -n "${var}" ]] && disk_name=$(lsblk -n -o NAME,TYPE,PTUUID | grep -w ${var} | grep -m1 'disk' | awk '{print $1}')
fi
if [ -z "${disk_name}" ]; then
echo -e "Ошибка определения имени диска: \"$@\"!"
press_enter "$FUNCNAME"
fi
echo "${disk_name}"
}
function disk_mounted {
# Возвращает точку монтирования
# "$@" - uuid
[[ -z "$@" ]] && return
local disk_name="$(disk_name $@)"
[[ -z "${disk_name}" ]] && return
echo "$(lsblk -n -l -o MOUNTPOINT /dev/${disk_name})"
}
function disk_hotplug {
# Проверка что диск "извлекаемый"
# $@ - имя диска
[[ -z "$@" ]] && return
local disk_hotplug="$(lsblk -n -d -l -o HOTPLUG /dev/$@ 2> /dev/null)"
if [ -z "${disk_hotplug}" ]; then
echo -e "Ошибка параметра: \"$@\"!"
press_enter "$FUNCNAME"
return
fi
disk_hotplug=${disk_hotplug// /''}
echo "${disk_hotplug}"
}
function disk_info {
# Создает файлы с разной информацией по диску
# использую sudo !!!
# "$1" - имя диска
# "$2" - куда писать файл с информацией
# "$3" - префикс файла
local disk_name="$(disk_parent_name "$1")"
local full_path="$2"
local prefix="$3"
local ptuid
[[ -z "${disk_name}" ]] && return
[[ -z "${full_path}" ]] && return
if [[ ! -d "${full_path}" ]]; then
echo -e "Не найден каталог: \"${full_path}\"!"
press_enter "$FUNCNAME"
return
fi
[[ -z "${prefix}" ]] && prefix="Info_"
local name_file="${prefix}$(date +%F).txt"
echo "sfdisk ${disk_name}:" > ${full_path}/${name_file}
echo '-----------' >> ${full_path}/${name_file}
sudo sfdisk -d /dev/${disk_name} >> ${full_path}/${name_file}
for var in "by-id" "by-uuid" "by-label" "by-partuuid"; do
echo -e "\nls -l ${var}:" >> ${full_path}/${name_file}
echo '------' >> ${full_path}/${name_file}
ls -l /dev/disk/${var}/ | grep "${disk_name}" | awk '{print $9, $10, $11}' >> ${full_path}/${name_file}
done
echo -e "\nlsblk:" >> ${full_path}/${name_file}
echo '------' >> ${full_path}/${name_file}
lsblk -o NAME,PTUUID,PARTUUID,UUID,PTTYPE,TYPE,LABEL,SIZE,MODEL,VENDOR,SERIAL /dev/${disk_name} >> ${full_path}/${name_file}
if [[ "${disk_name}" == mmc* ]]; then
if [ -n "$()command -v mmc" ]; then
name_file="${prefix}$(date +%F).info"
sudo mmc extcsd read /dev/${disk_name} > ${full_path}/${name_file}
fi
else
if [ -n "$(whereis smartctl | awk '{print $2}')" ]; then
name_file="${prefix}$(date +%F).smart"
sudo smartctl -a /dev/${disk_name} > ${full_path}/${name_file}
[[ -n $(grep "Unknown USB bridge" ${full_path}/${name_file}) ]] && rm ${full_path}/${name_file}
fi
fi
}
function disk_size {
# SIZE размер файловой системы
# FSAVAIL доступный размер файловой системы
# FSUSED использованный размер файловой системы
# FSUSE% использование файловой системы в процентах
# "$@" - имя диска
[[ -z "$@" ]] && return
local column
case "$2" in
FSAVAIL)
column="FSAVAIL"
;;
FSUSED)
column="FSUSED"
;;
*)
column="SIZE"
;;
esac
local human=''
[[ -z "$3" ]] && human='-b'
local disk_size
if [ -z $(ls -1 /dev/ | grep -Fx "$1") ]; then
press_enter "$FUNCNAME: Не найден диск \"$1\"!"
return
else
disk_size=$(lsblk -dn ${human} -o ${column} /dev/$1)
fi
echo "${disk_size//' '/''}"
}
function del_old_template_dirs {
# Удаляет каталоги с шаблоном в имени ${template}
# "$@" - строчка с параметрами
local full_path
# count - количество оставляемых файлов
local count
local prefix
local suffix
for var in "$@"; do [[ ${var} == *'='* ]] && eval ${var}; done
[[ -d "${full_path}" ]] || return
[[ -z "${count}" ]] || [[ "${count}" -lt "1" ]] && return
local dirs="$(ls -1rd ${full_path}/${prefix}${template}${suffix} 2> /dev/null)"
if [ -n "${dirs}" ]; then
for dir in ${dirs[@]}; do
[[ -d "${dir}" ]] || continue
let "count -= 1"
if [ "${count}" -lt "0" ]; then
rm -R ${dir}
if [ $? -ne 0 ]; then
whiptail_msgbox "Ошибка удаления каталога:\n${dir}"
fi
fi
done
fi
}
function del_old_template_files {
# Удаляет файлы с шаблоном в имени ${template}
# "$@" - строчка с параметрами
local full_path
# count - количество оставляемых файлов
local count
local prefix
local suffix
for var in "$@"; do [[ ${var} == *'='* ]] && eval ${var}; done
[[ -d "${full_path}" ]] || return
[[ -z "${count}" ]] || [[ "${count}" -lt "1" ]] && return
local files=$(ls -1r ${full_path}/${prefix}${template}${suffix} 2> /dev/null)
if [ -n "${files}" ]; then
for file in ${files[@]}; do
[[ -f "${file}" ]] || continue
let "count -= 1"
if [ "${count}" -lt "0" ]; then
rm ${file}
if [ $? -ne 0 ]; then
whiptail_msgbox "Ошибка удаления файла:\n${file}"
fi
fi
done
fi
}
# Функции для меню
function next_menu_index {
# Возвращает следующий индекс для пункта меню
# "$@" - предыдущий или "родительчкий" индекс
local index="$@"
local next_index=0
local length="${#index}"
local var=''
local next_menu_index=''
if [ ${#main_menu[@]} -eq 0 ]; then
next_menu_index="1"
elif [[ -z ${index} ]]; then
# индекс первого уровня
for key in ${!main_menu[@]}; do
(( ${key:0:1} > next_index )) && next_index=${key:0:1}
done
next_menu_index="$((next_index + 1))"
else
for key in ${!main_menu[@]}; do
if [[ ${key} == ${index}* ]]; then
var=${key:0:$((length + 2))}
if [ ${#var} -gt ${length} ]; then
(( ${var: -1} > next_index )) && next_index=${var: -1}
fi
fi
done
if [ -z "${var}" ]; then
# "родительский" индекс не найден!
for key in ${!main_menu[@]}; do
(( ${key:0:1} > next_index )) && next_index=${key:0:1}
done
next_menu_index="$((next_index + 1))"
else
next_menu_index="${index}.$((next_index + 1))"
fi
fi
echo "${next_menu_index}"
}
function menu_name {
# Возвращает имя пункта меню
[[ -z "$@" ]] && return
echo "$(echo "$@" | awk -F'=' '{print $1}')"
}
function menu_function {
# Возвращает имя функции для вызова, что назначена пункту меню
[[ -z "$@" ]] && return
echo "$(echo "$@" | awk -F'=' '{print $2}')"
}
function menu_rebuilding {
# Для частичного перестроения главного меню (main_menu)
# Вызывается команда ("$2") и после завершения работы команды
# вызывается функция создания меню в дополнительных скриптах
# с передачей индекса в эту функцию
# Использует ${breadcrumbs[-1]} массива скрипта BackUpData.bash!!!
# "$1" - имя функции меню для пересоздания
# "$2" - команда перед вызовом функции меню
# "$3" - каталог где запускать команду
[[ -z "$1" ]] && return
if [[ ! $(declare -F "$1") ]]; then
echo -e "\nНе найдена функция \"$1\"!"
press_enter "$FUNCNAME"
return
fi
if [ -n "$3" ] && ! [ -d "$3" ]; then
echo -e "\nНе найден каталог \"$3\"!"
press_enter "$FUNCNAME"
return
fi
if [ -n "$2" ]; then
if [[ -z "$(command -v "$2")" ]]; then
cd "$3"
bash
else
case "$2" in
mc)
mc "$3"
;;
ls)
ls -l "$3"
press_enter
;;
bash)
cd "$3"
bash
;;
esac
fi
fi
[[ -n "${breadcrumbs[-1]}" ]] && "$1" "${breadcrumbs[-1]:0:1}"
}
#=================================
# Загрузка дополнительных скриптов
#=================================
[[ -f "${Inclusions}/BackUpDir.bash" ]] && . ${Inclusions}/BackUpDir.bash
[[ -f "${Inclusions}/BackUpFile.bash" ]] && . ${Inclusions}/BackUpFile.bash
[[ -f "${Inclusions}/DdDisk.bash" ]] && . ${Inclusions}/DdDisk.bash
[[ -f "${Inclusions}/DdFlash.bash" ]] && . ${Inclusions}/DdFlash.bash
# [[ -f "${Inclusions}/RestoreDisk.bash" ]] && . ${Inclusions}/RestoreDisk.bash
# [[ -f "${Inclusions}/RestoreFlash.bash" ]] && . ${Inclusions}/RestoreFlash.bash
# [[ -f "${Inclusions}/Config.bash" ]] && . ${Inclusions}/Config.bash
# [[ -f "${Inclusions}/Help.bash" ]] && . ${Inclusions}/Help.bash
#===============================================================
# Главный ассоциативный массив имен меню и их вызываемых функций
#===============================================================
# Главное меню
declare -A main_menu
[[ $(declare -F backup_dir_menu) ]] && backup_dir_menu
[[ $(declare -F backup_file_menu) ]] && backup_file_menu
[[ $(declare -F dd_disk_menu) ]] && dd_disk_menu
[[ $(declare -F dd_flash_menu) ]] && dd_flash_menu
# [[ $(declare -F restore_disk_menu) ]] && restore_disk_menu
# [[ $(declare -F restore_flash_menu) ]] && restore_flash_menu
# для отладки
# -----------
[[ "$@" == *'lsblk_show'* ]] && [[ $(declare -F lsblk_show) ]] && lsblk_show
[[ "$@" == *'debug_menu_main'* ]] && debug_menu_main
# Текущий массив меню
declare -a menu_current
# Массив "хлебные крошки"
declare -a breadcrumbs
# для возвращения к выбранному пункту меню
default_item=''
#==============
# Основной цикл
#==============
while true; do
clear
main_item=''
unset menu_current
height=1
width=${#title_scr}
width_max=60
while [ ${#breadcrumbs[@]} -gt 0 ]; do
if [ -z "${main_menu[${breadcrumbs[-1]}]}" ]; then
unset breadcrumbs[-1]
else
break
fi
done
if [ ${#breadcrumbs[@]} = 0 ]; then
prefix=''
header="Главное меню"
else
prefix="${breadcrumbs[-1]}."
header="$(menu_name ${main_menu[${breadcrumbs[-1]}]})"
fi
(( ${#header} > width )) && width=${#header}
for ((i=1; i <= ${#main_menu[@]}; i++)); do
if [ -n "${main_menu["${prefix}${i}"]}" ]; then
main_item="$(menu_name ${main_menu["${prefix}${i}"]})"
menu_current=("${menu_current[@]}" "${i}" "${main_item}")
(( height++ ))
(( ${#main_item} > width )) && width=${#main_item}
fi
done
if [ ${#menu_current[@]} -eq 0 ]; then
if [ ${#main_menu[@]} -eq 0 ]; then
whiptail_msgbox "Главный массив меню пуст!"
break
else
whiptail_msgbox "Массив меню:\n\"${header}\"\nпуст!"
fi
fi
tag=$(whiptail --title "${title_scr}" --menu "${header}" --default-item "${default_item}" $(( height + 8 )) $(( width + 16 )) ${height} "${menu_current[@]}" 3>&1 1>&2 2>&3)
if [ $? -eq 0 ]; then
default_item=${tag}
menu_function="$(menu_function ${main_menu["${prefix}${tag}"]})"
if [ -z "${menu_function}" ]; then
whiptail_msgbox "Нет функции для меню:\n$(menu_name ${main_menu["${prefix}${i}"]})"
continue
elif [ "${menu_function}" == 'show_menu' ]; then
default_item=''
breadcrumbs+=("${prefix}${tag}")
else
# запуск дочернего процесса
# [[ "${menu_function}" != 'empty' ]] && (${menu_function})
# без запуска дочернего процесса?
[[ "${menu_function}" != 'empty' ]] && ${menu_function}
fi
else
if [ ${#breadcrumbs[@]} == 0 ]; then
break
else
default_item="${breadcrumbs[-1]: -1}"
unset breadcrumbs[-1]
fi
fi
done
exit 0
В принципе в самом скрипте есть пояснения.
Но есть существенные моменты:
- Скрипт НЕ создает меню! Пункты и подпункты меню создают подключаемые скрипты через заполнение массива главного меню (см. пример выше вызова BackUpData с параметром). Однако индексы массива главного меню формируются в этом скрипте и выдаются запросившему!
- Скрипт НЕ меняет массив главного меню! Он обеспечивает визуализацию меню/подменю («хлебные крошки») и запуск нужных функций или команд.
- Изменения массива главного меню могут делать только подключаемые скрипты! И только «своих» пунктов/подпунктов (если правильно написан подключаемый скрипт)!
Подключаемый скрипт BackUpDir
BackUpDir.bash
BackUpDir.bash
# =======
# Массивы
# =======
[[ -f "${ScriptCfg}/BackUpDir.arrays" ]] || return
. ${ScriptCfg}/BackUpDir.arrays
# локальные переменные для функций в этом скрипте
backup_dir_vars=("label" "dir_size")
# ======================
# Дополнительные функции
# ======================
function backup_dir_menu {
[[ ${#backup_dir_to_vars[@]} == 0 ]] && return
[[ ${#backup_dir_from[@]} == 0 ]] && return
[[ ${#backup_dir_to[@]} == 0 ]] && return
clear
echo "Подготовка меню (модуль $FUNCNAME)..."
local menu_name=" Создание архивных копий каталогов (всего каталогов: ${#backup_dir_from[@]})=show_menu"
local menu_index
if [ -z "$@" ]; then
menu_index="$(next_menu_index)"
main_menu["${menu_index}"]="${menu_name}"
else
menu_index="$@"
for key in ${!main_menu[@]}; do
[[ "${key}" == "${menu_index}."* ]] && unset main_menu[${key}]
done
fi
local submenu_index
local submenu_name
local last_backup
$(vars_declare_local "backup_dir_to_vars" "backup_dir_from" backup_dir_to)
for key_dir_from in $(arr_keys_sort "backup_dir_from"); do
vars_init "backup_dir_to_vars"
vars_set "backup_dir_from["${key_dir_from}"]"
# vars_test "backup_dir_to_vars"
submenu_index="$(next_menu_index ${menu_index})"
if [ ! -d "${from}/${key_dir_from}" ]; then
submenu_name="Не найден каталог \"$(replacement "${from}")/${key_dir_from}\""
main_menu["${submenu_index}"]="${submenu_name}=menu_rebuilding "$FUNCNAME""
continue
fi
dir_size=$(/usr/bin/du --exclude='*lost+found' --exclude='*.Trash-*' -sh ${from}/${key_dir_from}/ | awk '{print $1}')
submenu_name="Каталог \"$(replacement "${from}")/${key_dir_from}\" (${dir_size})"
for key_dir_to in $(arr_keys_sort "backup_dir_to"); do
[[ "${key_dir_to}" == ${key_dir_from}:* ]] || continue
vars_init "backup_dir_to_vars"
vars_set "backup_dir_to["${key_dir_to}"]"
[[ -z "${main_menu["${submenu_index}"]}" ]] && main_menu["${submenu_index}"]="${submenu_name}=show_menu"
to_for_menu="${to:0:14}...$(echo "${to}" | sed 's/.*\('${key_dir_to#*:*}'.*\).*/\1/')"
if [ ! -d "${to}" ]; then
submenu_name="Не найден каталог \"$(replacement "${to_for_menu}")\""
main_menu["$(next_menu_index ${submenu_index})"]="${submenu_name}=menu_rebuilding "$FUNCNAME""
continue
fi
if [ -z "${archivator}" ]; then
if [ -d "${to}/${prefix}_$(date +%F)" ]; then
submenu_name="Показать каталог с архивом за сегодня (\"$(replacement "${to_for_menu}")\")"
main_menu["$(next_menu_index ${submenu_index})"]="${submenu_name}=menu_rebuilding "$FUNCNAME" "mc" "${to}""
else
last_backup="$(find "${to}" -type d -iname "${prefix}_${template}" | sort | tail -1)"
if [[ -n "${last_backup}" ]]; then
last_backup=" (последний: ${last_backup##*/})"
last_backup=${last_backup//${prefix}_/''}
fi
submenu_name="Создать архив в \"$(replacement "${to_for_menu}")\" (свободно: "$(/usr/bin/df -h ${to} | tail -n1 | awk '{print $4}')")"${last_backup}""
main_menu["$(next_menu_index ${submenu_index})"]="${submenu_name}=backup_dir_run "${key_dir_from}" "${key_dir_to}""
fi
else
if [ -f "${to}/${prefix}_$(date +%F).$(archiver_file_ext ${archivator})" ]; then
submenu_name="Показать каталог с ${archivator}-архивом за сегодня (\"$(replacement "${to_for_menu}")\")"
main_menu["$(next_menu_index ${submenu_index})"]="${submenu_name}=menu_rebuilding "$FUNCNAME" "mc" "${to}""
else
last_backup="$(find "${to}" -type f -iname "${prefix}_${template}.$(archiver_file_ext ${archivator})" | sort | tail -1)"
if [[ -n "${last_backup}" ]]; then
last_backup=" (последний: ${last_backup##*/})"
last_backup=${last_backup//${prefix}_/''}
last_backup=${last_backup//.$(archiver_file_ext ${archivator})/''}
fi
submenu_name="Создать \"${archivator}\"-архив в \"$(replacement "${to_for_menu}")\" (свободно: "$(/usr/bin/df -h ${to} | tail -n1 | awk '{print $4}')")"${last_backup}""
main_menu["$(next_menu_index ${submenu_index})"]="${submenu_name}=backup_dir_run "${key_dir_from}" "${key_dir_to}""
fi
fi
done
done
}
function backup_dir_run {
# "$1" - ключ массива backup_dir_from
# "$2" - ключ массива backup_dir_to
clear
$(vars_declare_local "backup_dir_vars" "backup_dir_to_vars" backup_dir_to_vars)
vars_set "backup_dir_from["$1"]" "backup_dir_to["$2"]"
local name="${prefix}_$(date +%F)"
local dir_size="$(/usr/bin/du --exclude='*lost+found' --exclude='*.Trash-*' -sh ${from}/$1/ | awk '{print $1}')"
local archiver_params="$(archiver_params ${archivator})"
if [ -z "${archiver_params}" ]; then
echo -e "Копирование \"$(replacement "${from}")/$1\" (${dir_size})\n в \"$(replacement "${to}" 60)/${prefix}_$(date +%F)\"\n"
mkdir -p ${to}/${prefix}_$(date +%F)
if [ -d ${to}/${prefix}_$(date +%F) ]; then
cp -rT -P "${from}/$1" "{to}/${prefix}_$(date +%F)"
else
whiptail_msgbox "Ошибка создания каталога \"$(replacement "${to}" 60)/${prefix}_$(date +%F)\""
fi
else
echo -e "Архивирование \"$(replacement "${from}")/$1\" (${dir_size})\n в \"$(replacement "${to}" 60)/${prefix}_$(date +%F).$(archiver_file_ext ${archivator})\"\n"
cd ${from}
${archiver_params} ${to}/${prefix}_$(date +%F).$(archiver_file_ext ${archivator}) "$1"
fi
if [ $? -eq 0 ]; then
clear
if [ -z "${archiver_params}" ]; then
del_old_template_dirs "full_path=${to}" "count=${save_copies}" "prefix=${prefix}_"
else
del_old_template_files "full_path=${to}" "count=${save_copies}" "prefix=${prefix}_" "suffix=.$(archiver_file_ext ${archivator})"
fi
menu_rebuilding "backup_dir_menu"
else
# whiptail_msgbox "Ошибка архивирования каталога \"${prefix}_$(date +%F)\""
press_enter "Ошибка \"$?\" архивирования каталога \"${prefix}_$(date +%F)\" в \"$(replacement "${to}" 60)\""
menu_rebuilding "backup_dir_menu" "mc" "${to}"
fi
}
BackUpDir.arrays
BackUpDir.arrays
# допустимые значения "archivator": gzip, tar, 7z
backup_dir_to_vars=("prefix=${HOSTNAME}" "to" "save_copies=3" "archivator=7z")
declare -A backup_dir_from
declare -A backup_dir_to
media_rp4='Network/Home/WorkStations/nik-rp4/media'
BackUp="${HOME}/Network/Home/WorkStations/nik-rp4/media/BackUp"
[[ -d "/media/${USER}/BackUp" ]] && BackUp="/media/${USER}/BackUp"
case "${HOSTNAME}" in
asus-x55c)
backup_dir_from["SoftForNik"]="from=${HOME}/Work/HD"
backup_dir_to["SoftForNik:BackUp"]="to=${BackUp}/Data/HD/SoftForNik"
backup_dir_from["TC"]="from=${HOME}/Work"
backup_dir_to["TC:BackUp"]="to=${BackUp}/Data/TC save_copies=10"
;;
esac
Подключаемый скрипт BackUpFile
BackUpFile.bash
BackUpFile.bash
# =======
# Массивы
# =======
[[ -f "${ScriptCfg}/BackUpFile.arrays" ]] || return
. ${ScriptCfg}/BackUpFile.arrays
# локальные переменные для функций в этом скрипте
backup_file_vars=("label" "file_size")
# ======================
# Дополнительные функции
# ======================
function backup_file_menu {
[[ ${#backup_file_files_vars[@]} == 0 ]] && return
[[ ${#backup_file_files[@]} == 0 ]] && return
[[ ${#backup_file_to_vars[@]} == 0 ]] && return
[[ ${#backup_file_to[@]} == 0 ]] && return
clear
echo "Подготовка меню (модуль $FUNCNAME)..."
local menu_name=" Создание архивных копий файлов (всего файлов: ${#backup_file_files[@]})=show_menu"
local menu_index
if [ -z "$@" ]; then
menu_index="$(next_menu_index)"
main_menu["${menu_index}"]="${menu_name}"
else
menu_index="$@"
for key in ${!main_menu[@]}; do
[[ "${key}" == "${menu_index}."* ]] && unset main_menu[${key}]
done
fi
local submenu_index
local submenu_name
local last_backup
$(vars_declare_local "backup_file_vars" "backup_file_files_vars" backup_file_to_vars)
for key_file_files in $(arr_keys_sort "backup_file_files"); do
vars_init "backup_file_files_vars"
vars_set "backup_file_files["${key_file_files}"]"
# vars_test "backup_file_files_vars"
if [ -f "${from}/${name}" ]; then
file_size="$(ls -sh ${from}/${name} | awk '{print $1}')"
fi
submenu_index="$(next_menu_index ${menu_index})"
submenu_name="Файл \"${name}\" (${file_size})"
for key_file_to in $(arr_keys_sort "backup_file_to"); do
[[ "${key_file_to}" == ${key_file_files}:* ]] || continue
vars_init "backup_file_to_vars"
vars_set "backup_file_to["${key_file_to}"]"
if [[ ! -d "${from}" ]] && [[ "${from}" == "/media/${USER}"* ]]; then
label="${from//\/media\/${USER}\/''}"
label=${label//\/*/''}
[[ ! -d "/media/${USER}/${label}" ]] && continue 2
fi
[[ -z "${main_menu["${submenu_index}"]}" ]] && main_menu["${submenu_index}"]="${submenu_name}=show_menu"
if [[ ! -d "${from}" ]]; then
if [[ "${from}" == "/media/${USER}"* ]]; then
label="${from//\/media\/${USER}\/''}"
label=${label//\/*/''}
submenu_name="Не найден каталог \"$(replacement "${from}")\/${label}\//''}\" на \"${label}\""
main_menu["$(next_menu_index ${submenu_index})"]="${submenu_name}=menu_rebuilding "$FUNCNAME" mc "/media/${USER}/${label}""
continue 2
else
submenu_name="Не найден каталог \"$(replacement "${from}")\""
main_menu["$(next_menu_index ${submenu_index})"]="${submenu_name}=menu_rebuilding "$FUNCNAME""
continue 2
fi
fi
if [ ! -f "${from}/${name}" ]; then
submenu_name="Не найден файл в каталоге \"$(replacement "${from}")\""
main_menu["$(next_menu_index ${submenu_index})"]="${submenu_name}=menu_rebuilding "$FUNCNAME""
continue 2
fi
to_for_menu="${to:0:14}...$(echo "${to}" | sed 's/.*\('${key_file_to#*:*}'.*\).*/\1/')"
if [[ ! -d "${to}" ]]; then
if [[ "${to}" == "/media/${USER}"* ]]; then
label="${to//\/media\/${USER}\/''}"
label=${label//\/*/''}
if [ -d "/media/${USER}/${label}" ]; then
submenu_name="Не найден каталог \"$(replacement "${to_for_menu}")\/${label}\//''}\" на \"${label}\""
main_menu["$(next_menu_index ${submenu_index})"]="${submenu_name}=menu_rebuilding "$FUNCNAME" mc "/media/${USER}/${label}""
else
submenu_name="Диск \"${label}\" не подключен"
main_menu["$(next_menu_index ${submenu_index})"]="${submenu_name}=menu_rebuilding "$FUNCNAME""
fi
continue
else
submenu_name="Не найден каталог \"$(replacement "${to_for_menu}")\""
main_menu["$(next_menu_index ${submenu_index})"]="${submenu_name}=menu_rebuilding "$FUNCNAME""
continue
fi
fi
last_backup="$(find "${to}/" -type f -iname "${name%.*}_${template}.${name#*.}$(archiver_file_ext ${archivator})" | sort | tail -1)"
if [[ -n "${last_backup}" ]]; then
last_backup=" (последний: ${last_backup##*/})"
last_backup=${last_backup//${name%.*}_/''}
last_backup=${last_backup//$(archiver_file_ext ${archivator})/''}
last_backup=${last_backup//.${name#*.}/''}
fi
if [ -f "${to}/${name%.*}_$(date +%F).${name#*.}$(archiver_file_ext ${archivator})" ]; then
submenu_name="Показать каталог с архивом за сегодня в \"$(replacement "${to_for_menu}")\""
main_menu["$(next_menu_index ${submenu_index})"]="${submenu_name}=menu_rebuilding "$FUNCNAME" "mc" "${to}""
else
submenu_name="Создать архив в \"$(replacement "${to_for_menu}")\" (свободно: "$(/usr/bin/df -h ${to} | tail -n1 | awk '{print $4}')")"${last_backup}""
main_menu["$(next_menu_index ${submenu_index})"]="${submenu_name}=backup_file_run "${key_file_files}" "${key_file_to}""
fi
done
done
}
function backup_file_run {
# "$1" - ключ массива backup_file_files
# "$2" - ключ массива backup_file_to
clear
$(vars_declare_local "backup_file_vars" "backup_file_files_vars" backup_file_to_vars)
vars_set "backup_file_files["$1"]" "backup_file_to["$2"]"
file_size="$(ls -sh ${from}/${name} | awk '{print $1}')"
local archiver_params="$(archiver_params ${archivator})"
if [ -z "${archiver_params}" ]; then
echo -e "Копирование \"${name}\" (${file_size})\n в \"$(replacement "${to}" 60)\"\n"
if [ -n "${PvInstalled}" ]; then
pv ${from}/${name} > ${to}/${name%.*}_$(date +%F).${name#*.}
else
cp ${from}/${name} ${to}/${name%.*}_$(date +%F).${name#*.}
fi
else
echo -e "Архивирование \"${name}\" (${file_size})\n в \"$(replacement "${to}" 60)\"\n"
if [ -n "${PvInstalled}" ]; then
pv ${from}/${name} | ${archiver_params} > ${to}/${name%.*}_$(date +%F).${name#*.}$(archiver_file_ext ${archivator})
else
cat ${from}/${name} | ${archiver_params} > ${to}/${name%.*}_$(date +%F).${name#*.}$(archiver_file_ext ${archivator})
fi
fi
if [ $? -eq 0 ]; then
clear
del_old_template_files "full_path=${to}" "count=${save_files}" "prefix=${name%.*}_" "suffix=.${name#*.}$(archiver_file_ext ${archivator})"
menu_rebuilding "backup_file_menu" "mc" "${to}"
else
whiptail_msgbox "Ошибка \"$?\" архивирования файла \"${name}\" в \"$(replacement "${to}")\""
menu_rebuilding "backup_file_menu" "mc" "${to}"
fi
}
BackUpFile.arrays
BackUpFile.arrays
# допустимые значения "archivator": gzip, tar, 7z
backup_file_files_vars=("name" "from" "archivator=gzip")
declare -A backup_file_files
backup_file_to_vars=("to" "save_files=3")
declare -A backup_file_to
BackUp="${HOME}/Network/Home/WorkStations/nik-rp4/media/BackUp"
[[ -d "/media/${USER}/BackUp" ]] && BackUp="/media/${USER}/BackUp"
case "${HOSTNAME}" in
asus-x55c)
backup_file_files["w7-64.qcow2"]="name=w7-64.qcow2 from=${HOME}/Work/HD/Virt-Storage/AQEMU"
backup_file_to["w7-64.qcow2:BackUp"]="to=${BackUp}/Virt-Storage/${HOSTNAME}/AQEMU"
;;
esac
Подключаемый скрипт DdDisk
DdDisk.bash
DdDisk.bash
############################
# Dd-образы диска (под sudo)
############################
if [ -z "$(sudo -l | grep "NOPASSWD: ALL")" ]; then
echo "Для модуля \"Создание образов локальных дисков\""
echo "необходимы повышенные права пользователя \"$USER\"!"
echo "Для этого можно добавить в файл \"/etc/sudoers\" или"
echo "создать в каталоге \"/etc/sudoers.d\" любой файл"
echo "и добавить в него строчку ниже:"
echo "$USER $HOSTNAME=NOPASSWD: ALL"
press_enter
return
fi
# =======
# Массивы
# =======
[[ -f "${ScriptCfg}/DdDisk.arrays" ]] || return
. ${ScriptCfg}/DdDisk.arrays
# локальные переменные для функций в этом скрипте
dd_disks_vars=("dir_free" "disk_size" "disk_name" "dd_ext")
# ======================
# Дополнительные функции
# ======================
function dd_disk_menu {
[[ ${#dd_disks_init[@]} == 0 ]] && return
[[ ${#dd_disks_to[@]} == 0 ]] && return
[[ ${#dd_disks_from[@]} == 0 ]] && return
clear
echo "Подготовка меню (модуль $FUNCNAME)..."
local menu_name="Создание образов внутренних дисков=show_menu"
local menu_index
local submenu_index
local submenu_name
local last_backup
if [ -z "$@" ]; then
menu_index="$(next_menu_index)"
main_menu["${menu_index}"]="${menu_name}"
else
menu_index="$@"
for key in ${!main_menu[@]}; do
[[ "${key}" == "${menu_index}."* ]] && unset main_menu[${key}]
done
fi
$(vars_declare_local "dd_disks_vars" "dd_disks_init")
for key_disk_to in $(arr_keys_sort "dd_disks_to"); do
vars_set "dd_disks_to["${key_disk_to}"]"
# vars_test "dd_disks_vars" "dd_disks_init"
[[ -z "${main_menu["${menu_index}"]}" ]] && main_menu["${menu_index}"]="${menu_name}"
if [ ! -d "${to}" ]; then
submenu_index="$(next_menu_index ${menu_index})"
submenu_name="Не найден каталог \"$(replacement "${to}")\""
main_menu["${submenu_index}"]="${submenu_name}=empty"
continue
else
submenu_index="$(next_menu_index ${menu_index})"
dir_free="$(/usr/bin/df -h ${to} | tail -n1 | awk '{print $4}')"
submenu_name="Создание образов в \"$(replacement "${to}")\" (свободно: "${dir_free}")"
main_menu["${submenu_index}"]="${submenu_name}=show_menu"
for key_disk_from in $(arr_keys_sort "dd_disks_from"); do
if [[ ${key_disk_from} == ${key_disk_to}:* ]]; then
vars_init "dd_disks_init"
vars_set "dd_disks_from[${key_disk_from}]"
# vars_test "dd_disks_init"
disk_name="$(disk_name "${id_or_uuid}")"
if [ -z "${disk_name}" ]; then
submenu_name="Не найден диск \"${key_disk_from//${key_disk_to}':'/''}\""
main_menu["$(next_menu_index ${submenu_index})"]="${submenu_name}==empty"
continue
else
# на "asus-e410ma" это не сработало!
# if [ "$(disk_hotplug ${disk_name})" -ne "0" ]; then
# submenu_name="диск \"${disk_name}\" не является внутренним"
# main_menu["$(next_menu_index ${submenu_index})"]="${submenu_name}=empty"
# continue
# fi
if [ -n "disk_mounted ${id_or_uuid}" ]; then
if [ "$(/usr/bin/df ${to} | tail -n1)" == "$(/usr/bin/df /dev/$disk_name | tail -n1)" ]; then
submenu_name="Попытка создания образа \"${disk_name}\" в \"$(replacement "${to}")\" на диске \"${disk_name}\""
main_menu["${submenu_index}"]="${submenu_name}=empty"
continue
fi
fi
if [ -d "${to}/${save_dir}" ]; then
disk_size="$(disk_size ${disk_name} 'SIZE' 'human')"
dd_ext="$(archiver_dd_ext ${archivator})"
last_backup="$(find "${to}/${save_dir}/" -type f -iname "${disk_name}_${template}${dd_ext}" | sort | tail -1)"
if [[ -n "${last_backup}" ]]; then
last_backup=" (последний: ${last_backup##*/})"
last_backup=${last_backup//${disk_name}_/''}
last_backup=${last_backup//${dd_ext}/''}
fi
if [ -f "${to}/${save_dir}/${disk_name}_$(date +%F)${dd_ext}" ]; then
submenu_name="Показать каталог с образом \"${disk_name}\" за сегодня в \"$(replacement "${to}")\""
main_menu["$(next_menu_index ${submenu_index})"]="${submenu_name}=menu_rebuilding "$FUNCNAME" "mc" "${to}/${save_dir}""
else
submenu_name="Создать образ \"${disk_name}\" (${disk_size}) в \"$(replacement "${to}")\""${last_backup}""
main_menu["$(next_menu_index ${submenu_index})"]="${submenu_name}=dd_disk_run "${key_disk_to}" "${key_disk_from}""
fi
else
submenu_name="Каталог \"${save_dir}\" не найден в \"$(replacement "${to}")\""
main_menu["$(next_menu_index ${submenu_index})"]="${submenu_name}=menu_rebuilding "$FUNCNAME" "mc" "${to}""
fi
fi
fi
done
fi
done
}
function dd_disk_run {
clear
local key_disk_to="$1"
local key_disk_from="$2"
$(vars_declare_local "dd_disks_vars" "dd_disks_init")
vars_set "dd_disks_to["${key_disk_to}"]" "dd_disks_from[${key_disk_from}]"
# vars_test "dd_disks_init"
disk_name="$(disk_name "${id_or_uuid}")"
[[ -z "${disk_name}" ]] && return
local prefix="$(disk_parent_name ${disk_name})_"
disk_info ${disk_name} "${to}/${save_dir}" "${prefix}"
dd_ext="$(archiver_dd_ext ${archivator})"
local dd_if="/dev/${disk_name}"
local dd_of="${to}/${save_dir}/${disk_name}_$(date +%F)${dd_ext}"
disk_size="$(disk_size ${disk_name} 'SIZE' 'human')"
echo -e "Создание \"${dd_ext}\"-образа диска \"${disk_name}\" (${disk_size})\n в \"$(replacement "${to}" 60)\"\n"
disk_size=$(disk_size ${disk_name})
dir_free="$(/usr/bin/df -B1 ${to} | tail -n1 | awk '{print $4}')"
if [ "$(( disk_size * 12 / 10 ))" -gt "${dir_free}" ]; then
echo "Мало свободного места на \"$(replacement "${to}" 60)\"!"
press_enter "$FUNCNAME"
return
fi
local archiver_params="$(archiver_params ${archivator})"
if [ -z "${archiver_params}" ]; then
if [ -n "${PvInstalled}" ] && [ -n "${disk_size}" ]; then
sudo dd bs=1M if=${dd_if} | pv -s ${disk_size} > "${dd_of}"
else
sudo dd bs=1M status=progress if=${dd_if} of="${dd_of}"
fi
else
if [ -n "${PvInstalled}" ] && [ -n "${disk_size}" ]; then
sudo dd bs=1M if=${dd_if} | pv -s ${disk_size} | ${archiver_params} > "${dd_of}"
else
sudo dd bs=1M status=progress if=${dd_if} | ${archiver_params} > "${dd_of}"
fi
fi
if [ $? -eq 0 ]; then
clear
sudo chown $USER:$USER ${to}/${save_dir}/*
del_old_template_files "full_path=${to}/${save_dir}" "count=${save_files}" "prefix=${disk_name}_" "suffix=${dd_ext}"
del_old_template_files "full_path=${to}/${save_dir}" "count=${save_files}" "prefix=${prefix}" "suffix=.txt"
del_old_template_files "full_path=${to}/${save_dir}" "count=${save_files}" "prefix=${prefix}" "suffix=.smart"
del_old_template_files "full_path=${to}/${save_dir}" "count=${save_files}" "prefix=${prefix}" "suffix=.info"
menu_rebuilding "dd_disk_menu"
else
sudo chown $USER:$USER ${to}/${save_dir}/*
whiptail_msgbox "Ошибка \"$?\" создания образа \"${disk_name}\" в \"$(replacement "${to}" 60)\""
menu_rebuilding "dd_disk_menu" mc "${to}/${save_dir}"
fi
}
DdDisk.arrays
DdDisk.arrays
declare -A dd_disks_to
# допустимые значения "archivator": gzip, tar, 7z
dd_disks_init=("id_or_uuid" "save_dir" "save_files=5" "archivator=gzip")
declare -A dd_disks_from
VtData="${HOME}/Network/Home/WorkStations/nik-rp4/media/VtData"
[[ -d "/media/${USER}/VtData" ]] && VtData="/media/${USER}/VtData"
Archive="${HOME}/Network/Home/WorkStations/nik-rp4/media/Archive"
[[ -d "/media/${USER}/Archive" ]] && Archive="/media/${USER}/Archive"
case "${HOSTNAME}" in
asus-x55c)
dd_disks_to["VtData"]="to=${VtData}"
dd_disks_from["VtData:sda1"]="id_or_uuid=DA05-40F1 save_dir=Notebook/Asus-x55c save_files=20"
dd_disks_from["VtData:sda3"]="id_or_uuid=1800b4fa-5228-4698-949f-dbdc7e431b3e save_dir=Notebook/Asus-x55c save_files=20"
;;
esac
Подключаемый скрипт DdFlash
DdFlash.bash
DdFlash.bash
##########################
# Dd usb-флешек (под sudo)
##########################
if [ -z "$(sudo -l | grep "NOPASSWD: ALL")" ]; then
echo "Для модуля \"Создание образов флешек\""
echo "необходимы повышенные права пользователя \"$USER\"!"
echo "Для этого можно добавить в файл \"/etc/sudoers\" или"
echo "создать в каталоге \"/etc/sudoers.d\" любой файл"
echo "и добавить в него строчку ниже:"
echo "$USER $HOSTNAME=NOPASSWD: ALL"
press_enter
return
fi
# =======
# Массивы
# =======
[[ -f "${ScriptCfg}/DdFlash.arrays" ]] || return
. ${ScriptCfg}/DdFlash.arrays
# локальные переменные для функций в этом скрипте
dd_flash_vars=("flash_name" "flash_size" "dir_free" "dd_ext")
# ======================
# Дополнительные функции
# ======================
function flash_name {
# lsblk -n -l -o NAME,PTUUID,PARTUUID,UUID,TYPE,MOUNTPOINT
# "$@" - PTUUID флешки
[[ -z "$@" ]] && return
echo "$(lsblk -n -l -o NAME,PTUUID,TYPE | grep "$@" | grep 'disk' | awk '{print $1}')"
}
function dd_flash_menu {
[[ ${#dd_flash_init[@]} == 0 ]] && return
[[ ${#dd_flash_ptuuids[@]} == 0 ]] && return
[[ ${#dd_flash_dirs[@]} == 0 ]] && return
clear
echo "Подготовка меню (модуль $FUNCNAME)..."
local menu_index
local menu_name="Создание образов флешек (всего флешек: ${#dd_flash_ptuuids[@]})=show_menu"
local submenu_index
local submenu_name
if [ -z "$@" ]; then
menu_index="$(next_menu_index)"
main_menu["${menu_index}"]="${menu_name}"
else
menu_index="$@"
for key in ${!main_menu[@]}; do
[[ "${key}" == "${menu_index}."* ]] && unset main_menu[${key}]
done
fi
local last_backup
$(vars_declare_local "dd_flash_vars" "dd_flash_init")
# vars_test "dd_flash_vars" "dd_flash_init" "dd_flash_ptuuids"
# перебор флешек
for key_flash_ptuuid in $(arr_keys_sort "dd_flash_ptuuids"); do
vars_init "dd_flash_init"
vars_set "dd_flash_ptuuids[${key_flash_ptuuid}]"
# vars_test "dd_flash_init"
flash_name="$(flash_name "${flash_ptuuid}")"
if [[ -z "${flash_name}" ]]; then
submenu_index="$(next_menu_index ${menu_index})"
main_menu["${submenu_index}"]="Флешка: \"${key_flash_ptuuid}\" не подключена=empty"
continue
fi
if [ "$(disk_hotplug ${flash_name})" -ne "1" ]; then
submenu_index="$(next_menu_index ${menu_index})"
main_menu["${submenu_index}"]="\"${key_flash_ptuuid}\" не является флешкой=empty"
continue
fi
flash_size="$(lsblk -n -d -l -o SIZE /dev/${flash_name})"
flash_size="${flash_size//' '/''}"
submenu_index="$(next_menu_index ${menu_index})"
main_menu["${submenu_index}"]="Создание образа флешки \"${key_flash_ptuuid}\" (${flash_size})=show_menu"
# перебор каталогов
for key_flash_dir in $(arr_keys_sort "dd_flash_dirs"); do
if [[ ${key_flash_dir} == ${key_flash_ptuuid}:* ]]; then
vars_init "dd_flash_init"
vars_set "dd_flash_dirs[${key_flash_dir}]"
# vars_test "dd_flash_init"
dd_ext="$(archiver_dd_ext ${archivator})"
save_dir_for_menu="${save_dir:0:14}...$(echo "${save_dir}" | sed 's/.*\('${key_flash_dir#*:*}'.*\).*/\1/')"
if [[ -d "${save_dir}" ]]; then
last_backup="$(find "${save_dir}/" -type f -iname "${key_flash_ptuuid}_${template}${dd_ext}" | sort | tail -1)"
if [[ -n "${last_backup}" ]]; then
last_backup=" (последний: ${last_backup##*/})"
last_backup=${last_backup//${dd_ext}/''}
fi
if [ -f "${save_dir}/${key_flash_ptuuid}_$(date +%F)${dd_ext}" ]; then
submenu_name="Показать каталог с \"${dd_ext}\"-образом за сегодня в \"$(replacement "${save_dir_for_menu}")\""
main_menu["$(next_menu_index ${submenu_index})"]="${submenu_name}=menu_rebuilding "$FUNCNAME" "mc" "${save_dir}""
else
dir_free="$(/usr/bin/df -h ${save_dir} | tail -n1 | awk '{print $4}')"
submenu_name="Создать \"${dd_ext}\"-образ в \"$(replacement "${save_dir_for_menu}")\" (свободно: ${dir_free})"${last_backup}""
main_menu["$(next_menu_index ${submenu_index})"]="${submenu_name}=dd_flash_run "${key_flash_ptuuid}" "${key_flash_dir}""
fi
else
submenu_name="Не найден каталог \"$(replacement "${save_dir_for_menu}")\""
main_menu["$(next_menu_index ${submenu_index})"]="${submenu_name}=menu_rebuilding "$FUNCNAME" "mc" "${save_dir}""
fi
fi
done
done
}
function dd_flash_run {
clear
local key_flash_ptuuid="$1"
local key_flash_dir="$2"
$(vars_declare_local "dd_flash_vars" "dd_flash_init")
vars_set "dd_flash_ptuuids[${key_flash_ptuuid}]" "dd_flash_dirs[${key_flash_dir}]"
# vars_test "dd_flash_init"
flash_name="$(flash_name "${flash_ptuuid}")"
dd_ext="$(archiver_dd_ext ${archivator})"
local dd_if="/dev/$(flash_name "${flash_name}")"
local dd_of="${save_dir}/${key_flash_ptuuid}_$(date +%F)${dd_ext}"
disk_info "${flash_name}" "${save_dir}" "${key_flash_ptuuid}_"
[[ -z "${dd_if}" ]] && return
flash_size="$(disk_size ${flash_name} "SIZE" "human")"
echo -e "Создание \"${dd_ext}\"-образа флешки \"${key_flash_ptuuid}\" (${flash_size})\n в \"$(replacement "${save_dir}" 60)\"\n"
dir_free="$(/usr/bin/df -B1 ${save_dir} | tail -n1 | awk '{print $4}')"
flash_size=$(disk_size ${flash_name})
if [ "$(( flash_size * 15 / 10 ))" -gt "${dir_free}" ]; then
echo "Мало свободного места на \"$(replacement "${save_dir}" 60)\"!"
press_enter "$FUNCNAME"
return
fi
local archiver_params="$(archiver_params ${archivator})"
if [ -z "${archiver_params}" ]; then
if [ -n "${PvInstalled}" ] && [ -n "${flash_size}" ]; then
sudo dd bs=1M if=${dd_if} | pv -s ${flash_size} > "${dd_of}"
else
sudo dd bs=1M status=progress if=${dd_if} of="${dd_of}"
fi
else
if [ -n "${PvInstalled}" ] && [ -n "${flash_size}" ]; then
sudo dd bs=1M if=${dd_if} | pv -s ${flash_size} | ${archiver_params} > "${dd_of}"
else
sudo dd bs=1M status=progress if=${dd_if} | ${archiver_params} > "${dd_of}"
fi
fi
if [ $? -eq 0 ]; then
clear
sudo chown $USER:$USER ${save_dir}/*
del_old_template_files "full_path=${save_dir}" "count=${save_files}" "prefix=${key_flash_ptuuid}_" "suffix=${dd_ext}"
del_old_template_files "full_path=${save_dir}" "count=${save_files}" "prefix=${key_flash_ptuuid}_" "suffix=".txt""
menu_rebuilding "dd_flash_menu" "mc" "${save_dir}"
else
sudo chown $USER:$USER ${save_dir}/*
whiptail_msgbox "Ошибка \"$?\" создания образа флешки \"${key_flash_ptuuid}\"\n в \"$(replacement "${save_dir}" 60)\""
menu_rebuilding "dd_flash_menu" "mc" "${save_dir}"
fi
}
DdFlash.arrays
DdFlash.arrays
# Внимание! Для флешек используется PTUUID - идентификатор таблицы разделов (не зависит от карт-ридера)!
# Не путать с ID и UUID самих разделов!
# Для поиска можно использовать:
# lsblk -o NAME,PTUUID,PARTUUID,UUID,PTTYPE,TYPE,SIZE,MOUNTPOINT,HOTPLUG
# Или запустить скрипт с ключем lsblk_show
# допустимые значения "archivator": gzip, tar, 7z
dd_flash_init=("save_dir" "save_files=3" "archivator=gzip")
# индексы массива "dd_flash_ptuuids" условные и являются именами "конечного" каталога хранения образов флешки!
declare -A dd_flash_ptuuids
declare -A dd_flash_dirs
BackUp="${HOME}/Network/Home/WorkStations/nik-rp4/media/BackUp"
[[ -d /media/${USER}/BackUp ]] && BackUp="/media/${USER}/BackUp"
Archive="${HOME}/Network/Home/WorkStations/nik-rp4/media/Archive"
[[ -d /media/${USER}/Archive ]] && BackUp="/media/${USER}/Archive"
case "${HOSTNAME}" in
asus-x55c)
dd_flash_ptuuids["debian-12-64"]="flash_ptuuid=3678146b-1757-4fd3-a029-163f393382c8"
dd_flash_dirs["debian-12-64:BackUp"]="save_dir=${BackUp}/USB-flash/DebianLive/Debian-12"
if [[ -d ${Archive} ]]; then
dd_flash_dirs["debian-12-64:Archive"]="save_dir=${Archive}/USB-flash/DebianLive/Debian-12 save_files=2"
fi
dd_flash_ptuuids["kali-rp"]="flash_ptuuid=8f2fac4f"
dd_flash_dirs["kali-rp:BackUp"]="save_dir=${BackUp}/USB-flash/RaspberryPi/Kali-pi"
if [[ -d ${Archive} ]]; then
dd_flash_dirs["kali-rp:Archive"]="save_dir=${Archive}/USB-flash/RaspberryPi/Kali-pi save_files=2"
fi
;;
esac
Синхронизация (черновик)
Скрипт SyncData
SyncData.bash
SyncData.bash
#!/bin/bash
# set -e
# set -x
# Обрабатывает параметры: debug_menu_main
################
# Главный скрипт
################
clear
#===================
# Начальные проверки
#===================
if [ -z "$(command -v whiptail)" ]; then
echo "Не установлен пакет \"whiptail\"!"
exit 0
fi
# Это должно быть в самом начале!!!
name_scr=${0##*/}
name_scr=${name_scr%.*}
readonly name_scr
readonly title_scr="Синхронизация данных на \"${HOSTNAME}\" (${name_scr})"
readonly IFS_OLD=$IFS
# pgrep не понимает имен более 15 символов!
if [ $(pgrep -c "${name_scr:0:15}") -gt 1 ]; then
whiptail --title "${name_scr}" --msgbox "Скрипт уже запущен!" 7 50
exit
fi
# Должна быть первой функцией
function whiptail_msgbox {
[[ -z "$@" ]] && return
local strings="$@"
local lines=$(echo -e "${strings}" | wc -l)
local height=7
local height_max=12
[[ "${lines}" -gt "1" ]] && height=$((height + ${lines}))
local width=60
local width_max=80
IFS=$'\n'
for string in $(echo -e ${strings}); do
[[ "${#string}" -gt "${width}" ]] && width=${#string}
done
IFS=${IFS_OLD}
[[ "${width}" -gt "${width_max}" ]] && width=${width_max}
if [ "${height}" -gt "${height_max}" ]; then
whiptail --scrolltext --title "${title_scr}" --msgbox "${strings}" ${height_max} ${width}
else
whiptail --title "${title_scr}" --msgbox "${strings}" ${height} ${width}
fi
}
#================
# Общие константы
#================
# mc
readonly McInstalled="$(command -v mc)"
# meld
readonly MeldInstalled="$(command -v meld)"
# netcat (nc)
readonly NetcatInstalled="$(command -v netcat)"
# unison
readonly UnisonGtkInstalled="$(command -v unison-gtk)"
[[ -n "${UnisonGtkInstalled}" ]] && readonly UnisonVersion="$(unison -version | awk '{print $3}')"
# rsync
readonly RsyncInstalled="$(command -v rsync)"
[[ -n "${RsyncInstalled}" ]] && readonly RsyncVersion="$(rsync --version | awk '{print $3}')"
# Каталоги и файлы
readonly ScriptDir="$(cd -- "$(dirname -- "${0}")" &> /dev/null && pwd)"
readonly ScriptCfg="${ScriptDir}/${name_scr}Cfg"
if [ ! -d ${ScriptCfg} ]; then
whiptail_msgbox "Не найден каталог:\n${ScriptCfg}"
exit
fi
readonly Inclusions="${ScriptDir}/Inclusions"
readonly UnisonCfg="${ScriptDir}/UnisonCfg"
readonly UnisonIgnoreFile="${UnisonCfg}/IgnoreFiles.cfg"
#==============
# Общие функции
#==============
function press_enter {
# Вывод сообщений в консоль и ожидание нажатия Enter
# использовать echo здесь не получится
# из-за возврата результата через echo в некоторых вызывающих функциях!
set +x
if [ -z "$@" ]; then
read -p "\"Enter\" - продолжение, \"Ctrl+c\" - прервать"
else
read -p "\"$@\" \"Enter\" - продолжение, \"Ctrl+c\" - прервать"
fi
}
function arr_keys_sort {
# Возвращает отсортированную строку ключей массива
# "$1" - имя массива
[[ -z "$1" ]] && return
declare -n arr_keys_declare="$1"
if [[ ${#arr_keys_declare[@]} == 0 ]]; then
press_enter "$FUNCNAME: Массив \"$1\" не существует или пуст!"
return
fi
local sorted_arr_keys
IFS=$'\n'
sorted_arr_keys=($(sort <<< "${!arr_keys_declare[*]}"))
IFS=${IFS_OLD}
echo "${sorted_arr_keys[@]}"
}
function debug_menu_main {
# Показ массива главного меню для отладки
# Можно вставлять для отладки в разные места и передавать параметр $@
# Например передавать $FUNCNAME для отображения имени вызывающей функции
set +x
echo -e "\n##### menu_main ($@):"
for key in $(arr_keys_sort "main_menu"); do
echo "${key}: ${main_menu[${key}]}"
done
press_enter "$FUNCNAME"
}
function debug_menu_current {
set +x
echo -e "\n##### menu_current ($@):"
for element in "${menu_current[@]}"; do
echo "${element}"
done
press_enter "$FUNCNAME"
}
function replacement {
[[ -z "$1" ]] && return
local txt="$1"
txt="${txt//${HOME}/'$HOME'}"
txt="${txt//\/media\/${USER}/media}"
# Обрезание строки
local max_length=40
[[ -n "$2" ]] && max_length="$2"
if [ ${#txt} -gt $((max_length + 4)) ]; then
txt="${txt:0:$((max_length / 2))}...${txt:(-$((max_length / 2)))}"
fi
echo "${txt}"
}
function is_unison_run {
if [ -z "$1" ]; then
echo "$(pgrep -a unison-gtk)"
else
echo "$(pgrep -a unison-gtk | grep -w $1)"
fi
}
function vars_declare_local {
# Для создания локальных переменных чеsрез echo в вызывающей функции
# "$@" - имена массивов откуда брать переменные
[[ -z "$@" ]] && return
local result=''
for name_vars_declare in "$@"; do
declare -n arr_vars_declare="${name_vars_declare}"
if [[ ${#arr_vars_declare[@]} == 0 ]]; then
press_enter "$FUNCNAME: Массив \"${name_vars_declare}\" не существует или пуст!"
continue
fi
for var_vars_declare in ${arr_vars_declare[@]}; do
[[ -n "${result}" ]] && result+=' '
result+='local '${var_vars_declare}
done
done
echo -e "${result}"
}
function vars_init {
# Установка значений поумолчанию записанных в массивах
# Меняет переменные вызывающей функции!!!
# Применять после vars_declare_local или другого объявления локальных переменных в вызывающей функции!
# "$@" - имена массивов откуда брать переменные
[[ -z "$@" ]] && return
for name_vars_init in "$@"; do
declare -n arr_vars_init="${name_vars_init}"
if [[ ${#arr_vars_init[@]} == 0 ]]; then
echo -e "\nМассив \"${name_vars_init}\" не существует или пуст!"
press_enter "$FUNCNAME"
continue
fi
for var_vars_init in ${arr_vars_init[@]}; do
if [[ ${var_vars_init} == *'='* ]]; then eval ${var_vars_init}; else eval ${var_vars_init}=''; fi
done
done
}
function vars_set {
# Устанавливает актуальные значения переменных
# Меняет значения переменных в вызывающей функции!!!
# "$@" - элементы массивов откуда брать значения переменных
[[ -z "$@" ]] && return
local element_vars_set
for name_vars_set in "$@"; do
element_vars_set="${!name_vars_set}"
if [[ ${#element_vars_set[@]} == 0 ]]; then
echo -e "\nМассив \"${name_vars_set}\" не существует или пуст!"
press_enter "$FUNCNAME"
continue
fi
for var in "${element_vars_set[@]}"; do [[ ${var} == *'='* ]] && eval ${var}; done
done
}
function vars_test {
# Вывод пременных и их значений для тестирования
# Не применять если вызывающая функция возвращает что-то через echo!!!
# "$@" - имена массивов откуда брать переменные
[[ -z "$@" ]] && return
for name_vars_test in "$@"; do
echo -e "\nмассив переменных: ${name_vars_test}"
declare -n arr_vars_test="${name_vars_test}"
if [[ ${#arr_vars_test[@]} == 0 ]]; then
echo -e "\nМассив \"${name_vars_test}\" не существует или пуст!"
press_enter "$FUNCNAME"
continue
fi
for var_vars_test in ${arr_vars_test[@]}; do
[[ ${var_vars_test} == *'='* ]] && var_vars_test=${var_vars_test//'='*/''}
echo "${var_vars_test}:${!var_vars_test}"
done
done
press_enter "$FUNCNAME"
}
# Функции для меню
function next_menu_index {
local index="$@"
local next_index=0
local length="${#index}"
local var=''
local next_menu_index=''
if [ ${#main_menu[@]} -eq 0 ]; then
next_menu_index="1"
elif [[ -z ${index} ]]; then
# индекс первого уровня
for key in ${!main_menu[@]}; do
(( ${key:0:1} > next_index )) && next_index=${key:0:1}
done
next_menu_index="$((next_index + 1))"
else
for key in ${!main_menu[@]}; do
if [[ ${key} == ${index}* ]]; then
var=${key:0:$((length + 2))}
if [ ${#var} -gt ${length} ]; then
(( ${var: -1} > next_index )) && next_index=${var: -1}
fi
fi
done
if [ -z "${var}" ]; then
# "родительский" индекс не найден!
for key in ${!main_menu[@]}; do
(( ${key:0:1} > next_index )) && next_index=${key:0:1}
done
next_menu_index="$((next_index + 1))"
else
next_menu_index="${index}.$((next_index + 1))"
fi
fi
echo "${next_menu_index}"
}
function menu_name {
[[ -z "$@" ]] && return
echo "$(echo "$@" | awk -F'=' '{print $1}')"
}
function menu_function {
[[ -z "$@" ]] && return
echo "$(echo "$@" | awk -F'=' '{print $2}')"
}
function connection_check {
# "$@" - элемент массива откуда брать его значения
local error=''
local host=''
local port='22'
# whiptail_msgbox ниже не показывает окно, причину не понял!
if [ -z "$@" ]; then
error="Передан пустой параметр в connection_check!"
# whiptail_msgbox "${error}"
press_enter "${error}"
else
vars_set "$@"
if [ -z "${NetcatInstalled}" ]; then
error="Не установлен пакет \"netcat\"!"
# whiptail_msgbox "${error}"
press_enter "${error}"
elif [ -z "${host}" ] || [ -z "${port}" ]; then
error="Ошибка параметров в connection_check host: ${host} port: ${port}"
# $(whiptail_msgbox "${error}")
press_enter "${error}"
else
(nc -zw3 ${host} ${port})
if [ $? -gt 0 ]; then
error="Порт ${port} хоста ${host} недоступен!\n"
error+="Проверка: nc -zvw3 ${host} ${port}"
fi
fi
fi
echo "${error}"
}
function menu_rebuilding {
# для перестроения main_menu
# используется ${breadcrumbs[-1]} массива скрипта BackUpData.bash!!!
# "$1" - имя функции меню для пересоздания
# "$2" - команда перед вызовом функции меню
# "$3" - каталог где запускать команду
[[ -z "$1" ]] && return
if [[ ! $(declare -F "$1") ]]; then
echo -e "\nНе найдена функция \"$1\"!"
press_enter "$FUNCNAME"
return
fi
if [ -n "$3" ] && ! [ -d "$3" ]; then
echo -e "\nНе найден каталог \"$3\"!"
press_enter "$FUNCNAME"
return
fi
if [ -n "$2" ]; then
if [[ -z "$(command -v "$2")" ]]; then
cd "$3"
bash
else
case "$2" in
mc)
mc "$3"
;;
ls)
ls -l "$3"
press_enter
;;
bash)
cd "$3"
bash
;;
esac
fi
fi
[[ -n "${breadcrumbs[-1]}" ]] && "$1" "${breadcrumbs[-1]:0:1}"
}
#=================================
# Загрузка дополнительных скриптов
#=================================
[[ -f "${Inclusions}/UnisonRemote.bash" ]] && . ${Inclusions}/UnisonRemote.bash
[[ -f "${Inclusions}/UnisonLocal.bash" ]] && . ${Inclusions}/UnisonLocal.bash
[[ -n "${RsyncInstalled}" ]] && [[ -f "${Inclusions}/RsyncRemote.bash" ]] && . ${Inclusions}/RsyncRemote.bash
#===============================================================
# Главный ассоциативный массив имен меню и их вызываемых функций
#===============================================================
# Главное меню
declare -A main_menu
[[ $(declare -F unison_remote_menu) ]] && unison_remote_menu
[[ $(declare -F unison_local_menu) ]] && unison_local_menu
[[ $(declare -F rsync_remote_menu) ]] && rsync_remote_menu
# для отладки
# -----------
[[ "$@" == *'debug_menu_main'* ]] && debug_menu_main
# Текущий массив меню
declare -a menu_current
# Массив "хлебные крошки"
declare -a breadcrumbs
# для возвращения к выбранному пункту меню
default_item=''
#==============
# Основной цикл
#==============
while true; do
clear
main_item=''
unset menu_current
height=1
width=${#title_scr}
width_max=60
while [ ${#breadcrumbs[@]} -gt 0 ]; do
if [ -z "${main_menu[${breadcrumbs[-1]}]}" ]; then
# press_enter "breadcrumbs:${breadcrumbs[-1]}"
unset breadcrumbs[-1]
else
break
fi
done
if [ ${#breadcrumbs[@]} = 0 ]; then
prefix=''
header="Главное меню"
else
prefix="${breadcrumbs[-1]}."
header="$(menu_name ${main_menu[${breadcrumbs[-1]}]})"
fi
(( ${#header} > width )) && width=${#header}
for ((i=1; i <= ${#main_menu[@]}; i++)); do
if [ -n "${main_menu["${prefix}${i}"]}" ]; then
main_item="$(menu_name ${main_menu["${prefix}${i}"]})"
menu_current=("${menu_current[@]}" "${i}" "${main_item}")
(( height++ ))
(( ${#main_item} > width )) && width=${#main_item}
fi
done
if [ ${#menu_current[@]} -eq 0 ]; then
if [ ${#main_menu[@]} -eq 0 ]; then
whiptail_msgbox "Список главного меню пуст!"
break
else
whiptail_msgbox "Список меню:\n\"${header}\" пуст!"
fi
fi
tag=$(whiptail --title "${title_scr}" --menu "${header}" --default-item "${default_item}" $(( height + 8 )) $(( width + 16 )) ${height} "${menu_current[@]}" 3>&1 1>&2 2>&3)
if [ $? -eq 0 ]; then
default_item=${tag}
menu_function="$(menu_function ${main_menu["${prefix}${tag}"]})"
if [ -z "${menu_function}" ]; then
whiptail_msgbox "Нет функции для меню:\n$(menu_name ${main_menu["${prefix}${i}"]})"
continue
elif [ "${menu_function}" == 'show_menu' ]; then
default_item=''
breadcrumbs+=("${prefix}${tag}")
else
# запуск дочернего процесса
# [[ "${menu_function}" != 'empty' ]] && (${menu_function})
# без запуска дочернего процесса?
[[ "${menu_function}" != 'empty' ]] && ${menu_function}
fi
else
if [ ${#breadcrumbs[@]} == 0 ]; then
if [ -z "$(is_unison_run)" ]; then
break
else
whiptail_msgbox "Unison-gtk еще работает!"
fi
else
default_item="${breadcrumbs[-1]: -1}"
unset breadcrumbs[-1]
fi
fi
done
exit 0
Подключаемый скрипт RsyncRemote
RsyncRemote.bash
RsyncRemote.bash
##########################################
# Удаленная синхронизация rsync
# Аккуратно!!!
# Можно случайно удалить целые каталоги!!!
##########################################
# =======
# Массивы
# =======
[[ -f "${ScriptCfg}/RsyncRemote.arrays" ]] || return
. ${ScriptCfg}/RsyncRemote.arrays
rsync_remote_vars=("disk")
# ======================
# Дополнительные функции
# ======================
function rsync_remote_base_dirs_check {
# "$1" - индеск массива rsync_remote_connections
# "$2" - индеск массива rsync_remote_base_dirs
# возвращает через echo! vars_test и подобное не применять!
$(vars_declare_local "rsync_remote_connections_arr" "rsync_remote_base_dirs_arr")
vars_set "rsync_remote_connections["$1"]" "rsync_remote_base_dirs["$2"]"
local error_dir_remote=''
ssh -p ${port} ${user}@${host} [[ -d ${base_remote} ]] > /dev/null 2>&1
if [[ $? -ne 0 ]]; then
error_dir_remote="нет доступа к удаленному каталогу: \"${base_remote}\""
fi
echo "${error_dir_remote}"
}
function rsync_remote_menu {
[[ ${#rsync_remote_connections_arr[@]} == 0 ]] && return
[[ ${#rsync_remote_connections[@]} == 0 ]] && return
[[ ${#rsync_remote_base_dirs_arr[@]} == 0 ]] && return
[[ ${#rsync_remote_base_dirs[@]} == 0 ]] && return
[[ ${#rsync_remote_dirs_arr[@]} == 0 ]] && return
[[ ${#rsync_remote_dirs[@]} == 0 ]] && return
local menu_name="Синхронизация по сети (rsync)=show_menu"
echo "Подготовка меню: \"Синхронизация по сети (rsync)\"..."
sleep 1
if [ -z "$@" ]; then
menu_index="$(next_menu_index)"
main_menu["${menu_index}"]="${menu_name}"
else
menu_index="$@"
for key in ${!main_menu[@]}; do
[[ "${key}" == "${menu_index}."* ]] && unset main_menu[${key}]
done
fi
local submenu_index
local submenu_name
$(vars_declare_local "rsync_remote_vars" "rsync_remote_base_dirs_arr")
# vars_test "rsync_remote_vars" "rsync_remote_base_dirs_arr"
for key_connection in $(arr_keys_sort "rsync_remote_connections"); do
[[ -z "$(connection_check rsync_remote_connections["${key_connection}"])" ]] || continue
submenu_index="$(next_menu_index ${menu_index})"
main_menu["${submenu_index}"]="Синхронизация по сети с \"${key_connection}\" (rsync)=show_menu"
for key_base_dir in $(arr_keys_sort "rsync_remote_base_dirs"); do
if [[ ${key_base_dir} == ${key_connection}:* ]]; then
vars_init "rsync_remote_base_dirs_arr"
vars_set "rsync_remote_base_dirs["${key_base_dir}"]"
# vars_test "rsync_remote_vars" "rsync_remote_base_dirs_arr"
if [ ! -d ${base_locally} ]; then
if [[ "${base_locally}" == *"/media/${USER}"* ]]; then
disk="${base_locally//\/media\/${USER}\/''}"
disk=${disk//\/*/''}
if [ -d "/media/${USER}/${disk}" ]; then
submenu_name="не найден каталог \"$(replacement ${base_locally})\" на \"${disk}\""
main_menu["$(next_menu_index ${submenu_index})"]="${submenu_name}=menu_rebuilding "$FUNCNAME" mc "/media/${USER}/${disk}""
else
submenu_name="диск \"${disk}\" не подключен"
main_menu["$(next_menu_index ${submenu_index})"]="${submenu_name}=menu_rebuilding "$FUNCNAME""
fi
continue
else
submenu_name="не найден каталог \"$(replacement ${base_locally})\""
main_menu["$(next_menu_index ${submenu_index})"]="${submenu_name}=menu_rebuilding "$FUNCNAME""
continue
fi
fi
submenu_name="$(rsync_remote_base_dirs_check "${key_connection}" "${key_base_dir}")"
if [ -z "${submenu_name}" ]; then
submenu_name="удаленный:\"$(replacement ${base_remote})\" и локальный:\"$(replacement ${base_locally})\""
submenu_name=${submenu_name//\/media\/${USER}/media}
main_menu["$(next_menu_index ${submenu_index})"]="${submenu_name}=rsync_remote_sync "${key_connection}" "${key_base_dir}""
else
submenu_name=${submenu_name//\/media\/${USER}/media}
main_menu["$(next_menu_index ${submenu_index})"]="${submenu_name}=empty"
fi
fi
done
done
}
function rsync_remote_sync {
# "$1" - индеск массива rsync_remote_connections
# "$2" - индеск массива rsync_remote_dirs
clear
local key_connection="$1"
local key_base_dir="$2"
$(vars_declare_local "rsync_remote_connections_arr" "rsync_remote_base_dirs_arr" "rsync_remote_dirs_arr")
vars_set "rsync_remote_connections["${key_connection}"]" "rsync_remote_base_dirs["${key_base_dir}"]"
local version="$(ssh -p ${port} ${user}@${host} command -v rsync)"
if [ -z "${version}" ]; then
echo -e "\nНа хосте \"${host}\" не установлен \"rsync\"!"
press_enter
return
fi
local version="$(ssh -p ${port} ${user}@${host} rsync --version | head -n 1 | awk '{print $3}')"
local progress
local size
echo "Сихронизация данных с сервером ${host}..."
progress=''
size=0
for key_dir in ${!rsync_remote_dirs[@]}; do
if [[ ${key_dir} == "${key_base_dir}:"* ]]; then
vars_init "rsync_remote_dirs_arr"
vars_set "rsync_remote_dirs["${key_dir}"]"
if [ ! -d ${base_locally}/${locally} ]; then
echo -e "\nНа локальном компьютере нет каталога: ${base_locally}/${locally}"
press_enter "$FUNCNAME"
continue
fi
# написать проверку ошибки подключения!!!
ssh -p ${port} ${user}@${host} [[ -d ${base_remote}/${remote} ]] > /dev/null 2>&1
if [ $? -ne 0 ]; then
echo -e "\nНа ${host} нет каталога: ${base_remote}/${remote}"
press_enter "$FUNCNAME"
continue
fi
size=$(ssh -p ${port} ${user}@${host} du --exclude='*lost+found' --exclude='*.Trash-*' -sh --exclude='lost+found' ${base_remote}/${remote}/ | awk '{print $1}')
echo -e "\nКаталог: ${remote} (${size})"
echo "--------"
# progress='--progress'
rsync --exclude='lost+found' ${params} ${delete} ${progress} -e "ssh -p ${port}" "${user}@${host}:${base_remote}/${remote}/" "${base_locally}/${locally}"
if [ $? -ne 0 ]; then
echo -e "\nОшибка: $?"
press_enter "$FUNCNAME"
fi
if [[ -n "${pause}" ]]; then
echo -e "\nЗакончил с каталогом: ${remote}"
press_enter
fi
fi
done
echo -e "\nЗакончилась синхронизаця данных с сервером ${host}"
case "${execute_after}" in
mc)
[[ -n "${McInstalled}" ]] && mc "${base_locally}/${locally}" "${base_locally}"
;;
*)
press_enter
;;
esac
}
RsyncRemote.arrays
RsyncRemote.arrays
# ключи массива rsync_remote_connections условные, но участвуют в других массивах
rsync_remote_connections_arr=("host" "port=22" "user=${USER}")
declare -A rsync_remote_connections
# ключи массива rsync_remote_base_dirs условные, но участвуют в других массивах
rsync_remote_base_dirs_arr=("base_remote" "base_locally" "execute_after")
declare -A rsync_remote_base_dirs
# в "params" можно добавить долполнительно --progress и/или --stats
rsync_remote_dirs_arr=("remote" "locally" "params='-avhL'" "delete" "pause")
declare -A rsync_remote_dirs
case "${HOSTNAME}" in
"nik-vm")
media_rp4='rp4/media'
# smb
rsync_remote_connections["smb"]="host=smb.mh.loc user=root"
rsync_remote_base_dirs["smb:1"]="base_remote=/mnt/data base_locally=${HOME}/${media_rp4}/BackUp/Servers/smb/data"
rsync_remote_dirs["smb:1:1"]="remote=Distributiv locally=Distributiv params='-avhL' delete='--delete-during'"
rsync_remote_dirs["smb:1:4"]="remote=Work locally=Work params='-avhL'"
;;
esac
Подключаемый скрипт UnisonLocal
UnisonLocal.bash
UnisonLocal.bash
#########################
# Локальная синхронизация
#########################
if [ -z "$(command -v unison-gtk)" ]; then
echo "Для работы модуля \"Локальная синхронизация (unison)\""
echo "необходим установленный пакет \"unison-gtk\"!"
echo "Либо установите пакет, либо отключите загрузку этот модуля."
echo "Отключить модуль можно переименовав файл \"${Inclusions}/UnisonLocal.script\"."
press_enter
return
fi
# =======
# Массивы
# =======
[[ -f "${ScriptCfg}/UnisonLocal.arrays" ]] || return
. ${ScriptCfg}/UnisonLocal.arrays
unison_local_vars=("disk")
# ======================
# Дополнительные функции
# ======================
function unison_local_menu {
[[ ${#unison_local_dirs_arr[@]} == 0 ]] && return
[[ ${#unison_local_dirs[@]} == 0 ]] && return
local menu_index
local menu_name="Локальная синхронизация (unison)=show_menu"
if [ -z "$@" ]; then
menu_index="$(next_menu_index)"
main_menu["${menu_index}"]="${menu_name}"
else
menu_index="$@"
for key in ${!main_menu[@]}; do
[[ "${key}" == "${menu_index}."* ]] && unset main_menu[${key}]
done
fi
local submenu_name
# echo "Подготовка меню: \"${menu_name}\""
$(vars_declare_local "unison_local_vars" "unison_local_dirs_arr")
for key_dir in $(arr_keys_sort "unison_local_dirs"); do
# получаем переменные ${dir1} и ${dir2}
vars_set "unison_local_dirs["${key_dir}"]"
[[ -z "${dir1}" ]] && continue
[[ -z "${dir2}" ]] && continue
if [ ! -d ${dir1} ]; then
if [[ "${dir1}" == *"/media/${USER}"* ]]; then
disk="${dir1//\/media\/${USER}\/''}"
disk=${disk//\/*/''}
if [ -d "/media/${USER}/${disk}" ]; then
submenu_name="не найден каталог \"$(replacement ${dir1})\" на \"${disk}\""
main_menu["$(next_menu_index ${menu_index})"]="${submenu_name}=menu_rebuilding "$FUNCNAME" mc "/media/${USER}/${disk}""
else
submenu_name="диск \"${disk}\" не подключен"
main_menu["$(next_menu_index ${menu_index})"]="${submenu_name}=menu_rebuilding "$FUNCNAME""
fi
continue
else
submenu_name="не найден каталог \"$(replacement ${dir1})\""
main_menu["$(next_menu_index ${menu_index})"]="${submenu_name}=menu_rebuilding "$FUNCNAME""
continue
fi
fi
if [ ! -d ${dir2} ]; then
if [[ "${dir2}" == *"/media/${USER}"* ]]; then
disk="${dir2//\/media\/${USER}\/''}"
disk=${disk//\/*/''}
if [ -d "/media/${USER}/${disk}" ]; then
submenu_name="не найден каталог \"$(replacement ${dir2})\" на \"${disk}\""
main_menu["$(next_menu_index ${menu_index})"]="${submenu_name}=menu_rebuilding "$FUNCNAME" mc "/media/${USER}/${disk}""
else
submenu_name="диск \"${disk}\" не подключен"
main_menu["$(next_menu_index ${menu_index})"]="${submenu_name}=menu_rebuilding "$FUNCNAME""
fi
continue
else
submenu_name="не найден каталог \"$(replacement ${dir2})\""
main_menu["$(next_menu_index ${menu_index})"]="${submenu_name}=menu_rebuilding "$FUNCNAME""
continue
fi
fi
submenu_name="\"$(replacement ${dir1})\" и \"$(replacement ${dir2})\""
# submenu_name=${submenu_name//\/${USER}/}
main_menu["$(next_menu_index ${menu_index})"]="${submenu_name}=unison_local_sync ${key_dir}"
done
}
function unison_local_sync {
# параметр $@ - ключ массива unison_local_dirs
$(vars_declare_local "unison_local_dirs_arr")
vars_set "unison_local_dirs["$@"]"
local unison_cfg="${HOSTNAME}/${@}.cfg"
local UnisonProfile=${name_scr}_${HOSTNAME}_${@}.cfg
if [ ! -f "${UnisonCfg}/${unison_cfg}" ]; then
whiptail_msgbox "Нет конфиг-файла: \"${unison_cfg}\"\nдля индекса: \"$@\""
[[ -n "$(command -v mc)" ]] && mc ${UnisonCfg}
return
fi
if [ -n "$(is_unison_run ${UnisonProfile})" ]; then
whiptail_msgbox "Синхронизация\n\"${UnisonProfile}\"\nуже запущена!"
return
fi
echo "perms = 0" > $HOME/.unison/${UnisonProfile}
cat "${UnisonCfg}/${unison_cfg}" >> "$HOME/.unison/${UnisonProfile}"
cat "${UnisonIgnoreFile}" >> "$HOME/.unison/${UnisonProfile}"
if [ ! -f "$HOME/.unison/${UnisonProfile}" ]; then
whiptail_msgbox "Нет unison-профиля:\n$HOME/.unison/${UnisonProfile}"
return
fi
# label_str="${unison_cfg}"
# unison-gtk "${UnisonProfile}" "${dir1}" "${dir2}" -label "${label_str}" > /dev/null 2>&1 &
unison-gtk "${UnisonProfile}" "${dir1}" "${dir2}" > /dev/null 2>&1 &
}
UnisonLocal.arrays
UnisonLocal.arrays
# ключи массива unison_local_dirs условные,
# на их основе формируются имена файлов ${unison_cfg} в функции unison_local_sync
unison_local_dirs_arr=("dir1" "dir2")
declare -A unison_local_dirs
case "${HOSTNAME}" in
asus-x55c)
unison_local_dirs["home_-_projects_${HOSTNAME}_home"]="dir1=${HOME} dir2=${HOME}/Work/TC/Projects/VSCodium/Home/Data/WorkStations/Linux/${HOSTNAME}/${HOME}"
;;
esac
Подключаемый скрипт UnisonRemote
UnisonRemote.bash
UnisonRemote.bash
################################
# Удаленная синхронизация unison
################################
if [ -z "$(command -v unison-gtk)" ]; then
echo "Для работы модуля \"Синхронизация по сети (unison)\""
echo "необходим установленный пакет \"unison-gtk\"!"
echo "Либо установите пакет, либо отключите загрузку этот модуля."
echo "Отключить модуль можно переименовав файл \"${Inclusions}/UnisonRemote.script\"."
press_enter
return
fi
# =======
# Массивы
# =======
[[ -f "${ScriptCfg}/UnisonRemote.arrays" ]] || return
. ${ScriptCfg}/UnisonRemote.arrays
unison_remote_vars=("time")
# ======================
# Дополнительные функции
# ======================
function unison_remote_dirs_check {
# "$1" - элемент массива unison_remote_connections
# "$2" - элемент массива unison_remote_dirs
local error=''
$(vars_declare_local "unison_remote_dirs_arr" "unison_remote_connections_arr")
vars_set "$2"
[[ -z "${remote}" ]] && remote="${locally}"
if [ -z "$1" ] || [ -z "$2" ]; then
error="Не хватает параметров в \"unison_remote_dirs_check\" 1: $1, 2: $2!"
press_enter "${error}"
else
if [ ! -d "${locally}" ]; then
error="На локальном компьютере нет каталога: ${locally}"
else
vars_set "$1"
ssh -p ${port} ${user}@${host} [[ -d ${remote} ]] > /dev/null 2>&1
if [ $? -ne 0 ]; then
error="На удаленном компьютере нет каталога: ${remote}"
fi
fi
fi
echo "${error}"
}
function unison_remote_menu {
[[ ${#unison_remote_connections_arr[@]} == 0 ]] && return
[[ ${#unison_remote_connections[@]} == 0 ]] && return
[[ ${#unison_remote_dirs_arr[@]} == 0 ]] && return
[[ ${#unison_remote_dirs[@]} == 0 ]] && return
local menu_name="Синхронизация по сети (unison)=show_menu"
local menu_index="$(next_menu_index $@)"
main_menu["${menu_index}"]="${menu_name}"
local error=''
local submenu_index=''
local submenu_name
local menu_added=''
$(vars_declare_local "unison_remote_dirs_arr")
# echo "Подготовка меню: \"${menu_name}\""
for key_connection in $(arr_keys_sort "unison_remote_connections"); do
error="$(connection_check unison_remote_connections["${key_connection}"])"
if [ -z "${error}" ]; then
# есть соединение с удаленным компом
menu_added=''
submenu_index="$(next_menu_index ${menu_index})"
main_menu["${submenu_index}"]="Синхронизация по сети с \"${key_connection}\" (unison):=show_menu"
for key_dir in $(arr_keys_sort "unison_remote_dirs"); do
if [[ ${key_dir} == ${key_connection}:* ]]; then
error="$(unison_remote_dirs_check "unison_remote_connections["${key_connection}"]" "unison_remote_dirs["${key_dir}"]")"
if [ -z "${error}" ]; then
# все проверки успешны
vars_init "unison_remote_dirs_arr"
vars_set "unison_remote_dirs[${key_dir}]"
# vars_test "unison_remote_dirs_arr"
if [[ -z "${remote}" ]]; then
submenu_name="локальный и удаленный: \"$(replacement ${locally})\""
else
submenu_name="локальный: \"$(replacement ${locally})\" и удаленный: \"$(replacement ${remote})\""
fi
main_menu["$(next_menu_index ${submenu_index})"]="${submenu_name}=unison_remote_sync "unison_remote_connections["${key_connection}"]" "unison_remote_dirs["${key_dir}"]""
menu_added='yes'
fi
fi
done
if [ -z "${menu_added}" ]; then
main_menu["$(next_menu_index ${submenu_index})"]="Массив меню пуст!=empty"
fi
fi
done
}
function unison_remote_sync {
# $1 - это элемент массива "unison_remote_connections"
# $2 - это элемент массива "unison_remote_dirs"
if [ -z "${UnisonGtkInstalled}" ]; then
whiptail_msgbox "Не установлен пакет \"unison-gtk\"!"
return
fi
$(vars_declare_local "unison_remote_connections_arr" "unison_remote_dirs_arr")
vars_set "$1" "$2"
[[ -z "${remote}" ]] && remote="${locally}"
local version="$(ssh -p ${port} ${user}@${host} unison -version | awk '{print $3}')"
# version='2.48.4.2'
if [ "${version}" != "${UnisonVersion}" ]; then
whiptail_msgbox "Не совпадают версии unison:\nлокальная ${UnisonVersion}\nудаленная ${version}"
return
fi
local unison_cfg="${locally//${HOME}/home}_-_${host}_${remote//${HOME}/home}"
unison_cfg=${unison_cfg//\//'_'}
unison_cfg="${unison_cfg}.cfg"
local UnisonProfile="${name_scr}_${HOSTNAME}_${unison_cfg}"
if [ -n "$(is_unison_run ${UnisonProfile})" ]; then
whiptail_msgbox "Синхронизация\n${UnisonProfile}\nуже запущена!"
return
fi
if [ ! -f "${UnisonCfg}/${HOSTNAME}/${unison_cfg}" ]; then
whiptail_msgbox "Нет конфиг-файла:\n ${HOSTNAME}/${unison_cfg}"
return
fi
echo "root = ${locally}" > "${HOME}/.unison/${UnisonProfile}"
echo "root = ssh://${user}@${host}/${remote}" >> "${HOME}/.unison/${UnisonProfile}"
echo "sshargs = -C -p ${port}" >> "${HOME}/.unison/${UnisonProfile}"
# echo "label = Sync local \"${param}\" with ${host}" >> "${HOME}/.unison/${UnisonProfile}"
[[ -n "${MeldInstalled}" ]] && echo "diff = meld -u" >> "${HOME}/.unison/${UnisonProfile}"
echo "perms = 0" >> "${HOME}/.unison/${UnisonProfile}"
cat "${UnisonCfg}/${HOSTNAME}/${unison_cfg}" >> "${HOME}/.unison/${UnisonProfile}"
cat "${UnisonIgnoreFile}" >> "${HOME}/.unison/${UnisonProfile}"
if [ ! -f "${HOME}/.unison/${UnisonProfile}" ]; then
whiptail_msgbox "Нет unison-профиля:\n${HOME}/.unison/${UnisonProfile}"
return
fi
(unison-gtk "${UnisonProfile}") > /dev/null 2>&1 &
}
UnisonRemote.arrays
UnisonRemote.arrays
# ключи массива unison_remote_connections условные, но участвуют в других массивах
unison_remote_connections_arr=("host" "port=22" "user=${USER}")
declare -A unison_remote_connections
# профиль unison-а формируется из значений "locally" и "remote"
unison_remote_dirs_arr=("locally" "remote")
declare -A unison_remote_dirs
case "${HOSTNAME}" in
asus-x55c)
unison_remote_connections["nik-vm"]="host=nik-vm.mh.loc port=4444"
unison_remote_dirs["nik-vm:1"]="locally=${HOME}"
;;
esac












