Compare commits
6 Commits
f55223926f
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7ca246db3f | ||
|
|
a5ee241e15 | ||
|
|
810e6ec436 | ||
| 6af17fcb8e | |||
|
|
909b5bbb49 | ||
|
|
1738c35abb |
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
log*
|
||||||
56
generate_root_ca.sh
Executable file
56
generate_root_ca.sh
Executable file
@@ -0,0 +1,56 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# Настройки
|
||||||
|
CERT_DIR="$HOME/certs"
|
||||||
|
CA_NAME="myrootca"
|
||||||
|
DAYS=3650 # 10 лет
|
||||||
|
|
||||||
|
# Цвета
|
||||||
|
GREEN='\033[0;32m'
|
||||||
|
RED='\033[0;31m'
|
||||||
|
NC='\033[0m'
|
||||||
|
|
||||||
|
echo "Генерация корневого сертификата (Root CA)..."
|
||||||
|
|
||||||
|
# 1. Создание директории
|
||||||
|
mkdir -p "$CERT_DIR"
|
||||||
|
cd "$CERT_DIR" || exit 1
|
||||||
|
|
||||||
|
# Проверка: если CA уже существует, предупреждаем
|
||||||
|
if [ -f "${CA_NAME}.crt" ] && [ -f "${CA_NAME}.key" ]; then
|
||||||
|
echo -e "${RED}Внимание!${NC} Файлы ${CA_NAME}.crt и ${CA_NAME}.key уже существуют."
|
||||||
|
read -p "Перезаписать их? (это аннулирует все ранее выданные сертификаты) [y/N]: " confirm
|
||||||
|
if [[ ! $confirm =~ ^[Yy]$ ]]; then
|
||||||
|
echo "Отменено."
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 2. Генерация приватного ключа (4096 бит)
|
||||||
|
echo "Генерация приватного ключа..."
|
||||||
|
openssl genrsa -out "${CA_NAME}.key" 4096 2>/dev/null
|
||||||
|
chmod 600 "${CA_NAME}.key" # Защита ключа
|
||||||
|
|
||||||
|
# 3. Генерация самоподписанного сертификата
|
||||||
|
echo "Генерация корневого сертификата..."
|
||||||
|
openssl req -x509 -new -nodes \
|
||||||
|
-key "${CA_NAME}.key" \
|
||||||
|
-sha256 \
|
||||||
|
-days ${DAYS} \
|
||||||
|
-out "${CA_NAME}.crt" \
|
||||||
|
-subj "/C=RU/ST=State/L=City/O=MyOrg/CN=TrueNAS Root CA"
|
||||||
|
|
||||||
|
# Проверка результата
|
||||||
|
if [ -f "${CA_NAME}.crt" ]; then
|
||||||
|
echo -e "${GREEN}Успешно!${NC}"
|
||||||
|
echo "Файлы созданы в: $CERT_DIR"
|
||||||
|
echo " Ключ: ${CA_NAME}.key (храните в секрете!)"
|
||||||
|
echo " Серт: ${CA_NAME}.crt (установите этот файл на все клиентские устройства)"
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "Информация о сертификате:"
|
||||||
|
openssl x509 -in "${CA_NAME}.crt" -noout -subject -dates
|
||||||
|
else
|
||||||
|
echo -e "${RED}Ошибка при создании сертификата.${NC}"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
93
generate_signed_cert.sh
Executable file
93
generate_signed_cert.sh
Executable file
@@ -0,0 +1,93 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# Настройки
|
||||||
|
CERT_DIR="$HOME/certs"
|
||||||
|
CA_KEY="$CERT_DIR/truenasrootca.key"
|
||||||
|
CA_CRT="$CERT_DIR/truenasrootca.crt"
|
||||||
|
|
||||||
|
# Цвета для вывода
|
||||||
|
RED='\033[0;31m'
|
||||||
|
GREEN='\033[0;32m'
|
||||||
|
YELLOW='\033[1;33m'
|
||||||
|
NC='\033[0m' # No Color
|
||||||
|
|
||||||
|
# Функция для вывода ошибок
|
||||||
|
error() {
|
||||||
|
echo -e "${RED}[ERROR]${NC} $1" >&2
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
# Функция для вывода успеха
|
||||||
|
success() {
|
||||||
|
echo -e "${GREEN}[SUCCESS]${NC} $1"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Проверка аргументов
|
||||||
|
if [ "$#" -ne 3 ]; then
|
||||||
|
echo "Использование: $0 <имя_сертификата> <DNS_имя> <IP_адрес>"
|
||||||
|
echo "Пример: $0 truenas truenas.local 192.168.0.137"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
CERT_NAME="$1"
|
||||||
|
DNS_NAME="$2"
|
||||||
|
IP_ADDR="$3"
|
||||||
|
|
||||||
|
# Переход в директорию сертификатов
|
||||||
|
mkdir -p "$CERT_DIR"
|
||||||
|
cd "$CERT_DIR" || error "Не удалось перейти в директорию $CERT_DIR"
|
||||||
|
|
||||||
|
# 1. Проверка наличия корневого CA
|
||||||
|
if [ ! -f "$CA_KEY" ] || [ ! -f "$CA_CRT" ]; then
|
||||||
|
error "Корневой сертификат не найден! ($CA_CRT / $CA_KEY)"
|
||||||
|
echo "Сначала создайте Root CA или убедитесь, что файлы лежат в $CERT_DIR"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo -e "${YELLOW}Генерация сертификата для: ${CERT_NAME}${NC}"
|
||||||
|
echo "DNS: $DNS_NAME"
|
||||||
|
echo "IP: $IP_ADDR"
|
||||||
|
|
||||||
|
# 2. Генерация приватного ключа сервера
|
||||||
|
# (Пересоздаем ключ, если он уже есть, чтобы не было конфликтов)
|
||||||
|
openssl genrsa -out "${CERT_NAME}.key" 4096 2>/dev/null || error "Не удалось создать ключ"
|
||||||
|
chmod 600 "${CERT_NAME}.key" # Защита ключа
|
||||||
|
|
||||||
|
# 3. Создание файла расширений (v3.ext) динамически
|
||||||
|
cat > "${CERT_NAME}.ext" << EOF
|
||||||
|
authorityKeyIdentifier=keyid,issuer
|
||||||
|
basicConstraints=CA:FALSE
|
||||||
|
keyUsage = digitalSignature, keyEncipherment
|
||||||
|
subjectAltName = @alt_names
|
||||||
|
|
||||||
|
[alt_names]
|
||||||
|
DNS.1 = ${DNS_NAME}
|
||||||
|
IP.1 = ${IP_ADDR}
|
||||||
|
EOF
|
||||||
|
|
||||||
|
# 4. Создание CSR (Certificate Signing Request)
|
||||||
|
openssl req -new -key "${CERT_NAME}.key" -out "${CERT_NAME}.csr" \
|
||||||
|
-subj "/CN=${DNS_NAME}" 2>/dev/null || error "Не удалось создать CSR"
|
||||||
|
|
||||||
|
# 5. Подпись сертификата корневым CA
|
||||||
|
openssl x509 -req -in "${CERT_NAME}.csr" \
|
||||||
|
-CA "$CA_CRT" -CAkey "$CA_KEY" -CAcreateserial \
|
||||||
|
-out "${CERT_NAME}.crt" -days 3650 -sha256 \
|
||||||
|
-extfile "${CERT_NAME}.ext" 2>/dev/null || error "Не удалось подписать сертификат"
|
||||||
|
|
||||||
|
# 6. Очистка временных файлов (CSR и ext)
|
||||||
|
# Если вам нужен CSR для других целей, закомментируйте следующую строку
|
||||||
|
rm -f "${CERT_NAME}.csr" "${CERT_NAME}.ext"
|
||||||
|
|
||||||
|
# 7. Финальный вывод
|
||||||
|
success "Сертификат успешно создан!"
|
||||||
|
echo "Файлы:"
|
||||||
|
echo " Ключ: $CERT_DIR/${CERT_NAME}.key"
|
||||||
|
echo " Серт: $CERT_DIR/${CERT_NAME}.crt"
|
||||||
|
echo "Подписан с помощью: (использовать для установки доверия на клиентах)"
|
||||||
|
|
||||||
|
# Проверка содержимого
|
||||||
|
echo ""
|
||||||
|
echo "Информация о сертификате:"
|
||||||
|
openssl x509 -in "${CERT_NAME}.crt" -noout -text | grep -A1 "Subject Alternative Name"
|
||||||
|
|
||||||
142
scan_to_exe_and_set_x_flag.sh
Executable file
142
scan_to_exe_and_set_x_flag.sh
Executable file
@@ -0,0 +1,142 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# Совместим с TrueNAS CORE (FreeBSD) и SCALE (Linux)
|
||||||
|
# Безопасен при перенаправленном stdin
|
||||||
|
|
||||||
|
# Проверка наличия терминала для интерактивного режима
|
||||||
|
if [[ -t 0 ]]; then
|
||||||
|
INTERACTIVE=1
|
||||||
|
else
|
||||||
|
INTERACTIVE=0
|
||||||
|
echo "Внимание: stdin не подключён к терминалу. Режим подтверждения отключён."
|
||||||
|
echo "Используйте флаг --force для автоматического выполнения."
|
||||||
|
if [[ "$*" != *"--force"* ]]; then
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Цвета (только если вывод в терминал)
|
||||||
|
if [[ -t 1 ]]; then
|
||||||
|
RED='\033[0;31m'; GREEN='\033[0;32m'; YELLOW='\033[1;33m'; BLUE='\033[0;34m'; NC='\033[0m'
|
||||||
|
else
|
||||||
|
RED=''; GREEN=''; YELLOW=''; BLUE=''; NC=''
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Проверка аргумента
|
||||||
|
if [[ $# -lt 1 ]] || [[ "$1" == "--help" ]] || [[ "$1" == "-h" ]]; then
|
||||||
|
echo "Использование: $0 [--force] <путь_к_директории>"
|
||||||
|
echo " --force Пропустить подтверждение (для автоматизации)"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Обработка флага --force
|
||||||
|
FORCE=0
|
||||||
|
if [[ "$1" == "--force" ]]; then
|
||||||
|
FORCE=1
|
||||||
|
shift
|
||||||
|
fi
|
||||||
|
|
||||||
|
TARGET_DIR="$1"
|
||||||
|
|
||||||
|
# Проверка директории
|
||||||
|
if [[ ! -d "$TARGET_DIR" ]]; then
|
||||||
|
echo -e "${RED}Ошибка:${NC} Директория не существует: $TARGET_DIR" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Нормализация пути
|
||||||
|
TARGET_DIR="$(cd "$TARGET_DIR" 2>/dev/null && pwd || echo "$TARGET_DIR")"
|
||||||
|
|
||||||
|
echo -e "${BLUE}========================================${NC}"
|
||||||
|
echo -e "${BLUE}Подготовка к обработке Windows-файлов${NC}"
|
||||||
|
echo -e "${BLUE}========================================${NC}"
|
||||||
|
echo ""
|
||||||
|
echo -e "Директория: ${YELLOW}${TARGET_DIR}${NC}"
|
||||||
|
echo -e "Расширения: ${YELLOW}.exe .bat .cmd .com${NC} (любой регистр)"
|
||||||
|
echo -e "Права: ${YELLOW}ug+x${NC} (владелец + группа)"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# Подсчёт файлов (безопасный метод для BSD)
|
||||||
|
FILE_COUNT=0
|
||||||
|
while IFS= read -r -d '' file 2>/dev/null; do
|
||||||
|
((FILE_COUNT++)) 2>/dev/null || FILE_COUNT=$((FILE_COUNT + 1))
|
||||||
|
done < <(find "$TARGET_DIR" -type f \( -iname "*.exe" -o -iname "*.bat" -o -iname "*.cmd" -o -iname "*.com" \) -print0 2>/dev/null || true)
|
||||||
|
|
||||||
|
echo -e "Найдено файлов: ${GREEN}${FILE_COUNT}${NC}"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
if [[ $FILE_COUNT -eq 0 ]]; then
|
||||||
|
echo -e "${YELLOW}Не найдено подходящих файлов. Обработка не требуется.${NC}"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Предпросмотр (только если интерактивно)
|
||||||
|
if [[ $INTERACTIVE -eq 1 ]]; then
|
||||||
|
echo -e "${BLUE}Примеры найденных файлов (первые 10):${NC}"
|
||||||
|
COUNT_PREVIEW=0
|
||||||
|
while IFS= read -r -d '' file 2>/dev/null; do
|
||||||
|
echo " • $file"
|
||||||
|
COUNT_PREVIEW=$((COUNT_PREVIEW + 1))
|
||||||
|
[[ $COUNT_PREVIEW -ge 10 ]] && break
|
||||||
|
done < <(find "$TARGET_DIR" -type f \( -iname "*.exe" -o -iname "*.bat" -o -iname "*.cmd" -o -iname "*.com" \) -print0 2>/dev/null || true)
|
||||||
|
|
||||||
|
if [[ $FILE_COUNT -gt 10 ]]; then
|
||||||
|
echo " ... и ещё $((FILE_COUNT - 10)) файлов"
|
||||||
|
fi
|
||||||
|
echo ""
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Подтверждение (только если интерактивно и не --force)
|
||||||
|
if [[ $INTERACTIVE -eq 1 && $FORCE -eq 0 ]]; then
|
||||||
|
echo -e "${YELLOW}Внимание:${NC} Будут изменены права ${GREEN}ТОЛЬКО${NC} для владельца и группы (ug+x)"
|
||||||
|
echo -n "Продолжить? (y/n) [n]: "
|
||||||
|
read -r CONFIRM || CONFIRM="n"
|
||||||
|
|
||||||
|
case "${CONFIRM,,}" in
|
||||||
|
y|yes|д|да)
|
||||||
|
echo -e "\n${GREEN}Подтверждено. Начинаем обработку...${NC}"
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
echo -e "\n${YELLOW}Операция отменена.${NC}"
|
||||||
|
exit 0
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
echo ""
|
||||||
|
elif [[ $FORCE -eq 1 ]]; then
|
||||||
|
echo -e "${YELLOW}Режим --force: подтверждение пропущено.${NC}"
|
||||||
|
echo ""
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Обработка файлов
|
||||||
|
SUCCESS=0
|
||||||
|
FAILED=0
|
||||||
|
TOTAL=0
|
||||||
|
|
||||||
|
while IFS= read -r -d '' file 2>/dev/null; do
|
||||||
|
TOTAL=$((TOTAL + 1))
|
||||||
|
if chmod ug+x "$file" 2>/dev/null; then
|
||||||
|
BASENAME="${file##*/}"
|
||||||
|
echo -e "[${GREEN}OK${NC}] $BASENAME"
|
||||||
|
SUCCESS=$((SUCCESS + 1))
|
||||||
|
else
|
||||||
|
BASENAME="${file##*/}"
|
||||||
|
echo -e "[${RED}FAIL${NC}] $BASENAME (нет прав?)"
|
||||||
|
FAILED=$((FAILED + 1))
|
||||||
|
fi
|
||||||
|
done < <(find "$TARGET_DIR" -type f \( -iname "*.exe" -o -iname "*.bat" -o -iname "*.cmd" -o -iname "*.com" \) -print0 2>/dev/null || true)
|
||||||
|
|
||||||
|
# Итог
|
||||||
|
echo ""
|
||||||
|
echo -e "${BLUE}========================================${NC}"
|
||||||
|
echo -e "${BLUE}Итог обработки${NC}"
|
||||||
|
echo -e "${BLUE}========================================${NC}"
|
||||||
|
echo -e "Успешно: ${GREEN}${SUCCESS}${NC}"
|
||||||
|
echo -e "Ошибок: ${RED}${FAILED}${NC}"
|
||||||
|
echo -e "Всего: ${BLUE}${TOTAL}${NC}"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
if [[ $FAILED -gt 0 ]]; then
|
||||||
|
echo -e "${YELLOW}Внимание:${NC} Некоторые файлы не обработаны (возможно, нет прав на запись)."
|
||||||
|
echo "Проверьте: mount -v | grep '${TARGET_DIR%/*}' — возможно, ФС смонтирована как read-only."
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo -e "${GREEN}Готово.${NC}"
|
||||||
130
zfs_send.zsh
130
zfs_send.zsh
@@ -2,16 +2,12 @@
|
|||||||
|
|
||||||
#TODO list
|
#TODO list
|
||||||
# 1. Скрипт не определяет, что нужно остановиться
|
# 1. Скрипт не определяет, что нужно остановиться
|
||||||
# 2. Возникают ошибки:
|
# 2. Нужно автоматически определить, если копирование уже запущено то предложить остановить и вывести процесс резервирования
|
||||||
# sending main_pool/3D_Design@manual-20250327 (99%: 21.9G/21.9G) 25.6M/s date: invalid date ‘@0.’
|
# Нет проверки на верность введённых параметров
|
||||||
# date: invalid date ‘@954.’
|
|
||||||
# sending main_pool/3D_Design@manual-20250327 (100%: 21.9G/21.9G) 25.6M/s date: invalid date ‘@-1.’
|
|
||||||
# date: invalid date ‘@954.’
|
|
||||||
# 3. Нужно автоматически определить, если копирование уже запущено то предложить остановить и вывести процесс резервирования
|
|
||||||
# смотреть нужно сразу на всей системе. Не будем запускать несколько параллельных резервирований.
|
# смотреть нужно сразу на всей системе. Не будем запускать несколько параллельных резервирований.
|
||||||
|
|
||||||
|
|
||||||
backup_server="192.168.0.162" # Сервер для резервирования
|
backup_server="192.168.0.120" # Сервер для резервирования
|
||||||
backup_user="root" # Пользователь на сервере
|
backup_user="root" # Пользователь на сервере
|
||||||
inc_snapshot="manual-$(date +%Y%m%d)" # Новый создаваемый снисмок
|
inc_snapshot="manual-$(date +%Y%m%d)" # Новый создаваемый снисмок
|
||||||
incremental=false
|
incremental=false
|
||||||
@@ -51,7 +47,7 @@ while [[ "$#" -gt 0 ]]; do
|
|||||||
echo " --no-check";
|
echo " --no-check";
|
||||||
echo " --stop";
|
echo " --stop";
|
||||||
echo "-i | --incremental" ;
|
echo "-i | --incremental" ;
|
||||||
echo "-p | --progress" ;;
|
exit 0;;
|
||||||
*) echo "Неизвестные параметры: $1"; exit 1 ;;
|
*) echo "Неизвестные параметры: $1"; exit 1 ;;
|
||||||
esac
|
esac
|
||||||
shift
|
shift
|
||||||
@@ -90,16 +86,20 @@ find_ssh_key() {
|
|||||||
done
|
done
|
||||||
|
|
||||||
# Если ключей нет, создаем новый
|
# Если ключей нет, создаем новый
|
||||||
|
if [ ! -f ~/.ssh/id_ed25519 ]; then
|
||||||
|
ssh-keygen -t ed25519 -N "" -f ~/.ssh/id_ed25519
|
||||||
# echo "Создаём новый ключ..."
|
# echo "Создаём новый ключ..."
|
||||||
ssh-keygen -t ed25519 -N "" -f ~/.ssh/id_ed25519 >&2
|
ssh-keygen -t ed25519 -N "" -f ~/.ssh/id_ed25519 >&2
|
||||||
echo "~/.ssh/id_ed25519"
|
echo "~/.ssh/id_ed25519"
|
||||||
|
else
|
||||||
|
echo "Ключей для этого сервера не найдено. Ключ по умолчанию ~/.ssh/id_ed25519 уже существует. Создание нового ключа прервано"
|
||||||
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
# Определение ключа
|
# Определение ключа
|
||||||
key_path=$(find_ssh_key)
|
key_path=$(find_ssh_key)
|
||||||
key_pub_path="${key_path}.pub"
|
key_pub_path="${key_path}.pub"
|
||||||
|
|
||||||
|
|
||||||
check_ssh_connection() {
|
check_ssh_connection() {
|
||||||
ssh -o "BatchMode=yes" -o "ConnectTimeout=5" "$backup_user@$backup_server" exit 2>/dev/null
|
ssh -o "BatchMode=yes" -o "ConnectTimeout=5" "$backup_user@$backup_server" exit 2>/dev/null
|
||||||
return $?
|
return $?
|
||||||
@@ -210,6 +210,19 @@ local_datasets=(${=local_datasets}) # Конвертирование в масс
|
|||||||
backup_datasets=$(ssh $backup_user@$backup_server zfs list -o name | grep -v -e ix-app -e boot-pool -e system-data -e jails -e NAME)
|
backup_datasets=$(ssh $backup_user@$backup_server zfs list -o name | grep -v -e ix-app -e boot-pool -e system-data -e jails -e NAME)
|
||||||
backup_datasets=(${=backup_datasets}) # Конвертирование в массив
|
backup_datasets=(${=backup_datasets}) # Конвертирование в массив
|
||||||
|
|
||||||
|
# Свободное место на бэкап-сервере
|
||||||
|
declare -A backup_pool_free # Словарь: ключ = пул, значение = свободное место
|
||||||
|
ignored_pools=(boot-pool)
|
||||||
|
|
||||||
|
# Запрашиваем данные о свободном пространстве и парсим
|
||||||
|
while read -r pool free; do
|
||||||
|
if (( ${ignored_pools[(Ie)$pool]} )); then
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
backup_pool_free[$pool]="$free"
|
||||||
|
done < <(ssh "$backup_user@$backup_server" "zpool list -H -o name,free" 2>/dev/null)
|
||||||
|
|
||||||
|
|
||||||
declare -A work_datasets # Словарь ключ = резервируемый локальный датасет, значение = имя датасета на удалённом сервере
|
declare -A work_datasets # Словарь ключ = резервируемый локальный датасет, значение = имя датасета на удалённом сервере
|
||||||
|
|
||||||
# Если заданы local_dataset & remote_dataset
|
# Если заданы local_dataset & remote_dataset
|
||||||
@@ -282,12 +295,13 @@ fi # Конец блока наличия переданных параметр
|
|||||||
# Словарь со списком последких снапшотов для датасетов
|
# Словарь со списком последких снапшотов для датасетов
|
||||||
declare -A last_loc_snaps
|
declare -A last_loc_snaps
|
||||||
echo -ne "Проверяю на наличие $inc_snapshot...\033[2K\r"
|
echo -ne "Проверяю на наличие $inc_snapshot...\033[2K\r"
|
||||||
|
column_widths="%-45s * %-45s\n"
|
||||||
for loc_ds bak_ds in ${(kv)work_datasets}; do
|
for loc_ds bak_ds in ${(kv)work_datasets}; do
|
||||||
# Запросить последние снимки
|
# Запросить последние снимки
|
||||||
loc_snap="$(zfs list -t snapshot -o name ${loc_ds} | grep 'manual' | tail -n1 | egrep -o '@.+' )"
|
loc_snap="$(zfs list -t snapshot -o name ${loc_ds} 2>/dev/null | grep 'manual' | tail -n1 | egrep -o '@.+' )"
|
||||||
loc_snap=${loc_snap:1} # удалить @ в начале строки
|
loc_snap=${loc_snap:1} # удалить @ в начале строки
|
||||||
if [[ $incremental = true ]]; then
|
if [[ $incremental = true ]]; then
|
||||||
bak_snap=$(ssh $backup_user@$backup_server zfs list -t snapshot -o name ${bak_ds} | grep 'manual' | tail -n1 | egrep -o '@.+')
|
bak_snap=$(ssh $backup_user@$backup_server zfs list -t snapshot -o name ${bak_ds} 2>/dev/null | grep 'manual' | tail -n1 | egrep -o '@.+')
|
||||||
bak_snap=${bak_snap:1} # удалить @ в начале строки
|
bak_snap=${bak_snap:1} # удалить @ в начале строки
|
||||||
fi
|
fi
|
||||||
# echo "$loc_ds @ $loc_snap -> $bak_ds @ $bak_snap"
|
# echo "$loc_ds @ $loc_snap -> $bak_ds @ $bak_snap"
|
||||||
@@ -295,12 +309,12 @@ for loc_ds bak_ds in ${(kv)work_datasets}; do
|
|||||||
if [[ "$loc_snap" = "$inc_snapshot" ]]; then
|
if [[ "$loc_snap" = "$inc_snapshot" ]]; then
|
||||||
if [[ $incremental = true ]] && [[ "$loc_snap" = "$bak_snap" ]]; then
|
if [[ $incremental = true ]] && [[ "$loc_snap" = "$bak_snap" ]]; then
|
||||||
# Если последние снимки на обеих машинах совпадают в инкрементном режиме
|
# Если последние снимки на обеих машинах совпадают в инкрементном режиме
|
||||||
echo "${loc_ds}@${loc_snap} * существует на обеих машинах. Исключено из списка резервируемых"
|
printf $column_widths "${loc_ds}@${loc_snap}" "существует на обеих машинах. Исключено из списка резервируемых"
|
||||||
unset "work_datasets[$loc_ds]"
|
unset "work_datasets[$loc_ds]"
|
||||||
else
|
else
|
||||||
# Если на удалённой машине нет такого снимка, то предложить удалить.
|
# Если на удалённой машине нет такого снимка, то предложить удалить.
|
||||||
echo "Датасет $loc_ds уже имеет последний снимок $inc_snapshot"
|
printf $column_widths "${loc_ds}@${loc_snap}" "существует на исходной машине, но отсутвует на $backup_server"
|
||||||
read "act?ok: резервировать | re: пересоздать (удаляет сразу!) | исключить из работы по умолчанию : "
|
read "act?ok: резервировать | re: пересоздать (удаляет сразу!!!) | по умолчанию исключить из работы : "
|
||||||
case $act in
|
case $act in
|
||||||
re) zfs destroy "${loc_ds}@${loc_snap}";
|
re) zfs destroy "${loc_ds}@${loc_snap}";
|
||||||
loc_snap="$(zfs list -t snapshot -o name ${loc_ds} | grep 'manual' | tail -n1 | egrep -o '@.+' )";
|
loc_snap="$(zfs list -t snapshot -o name ${loc_ds} | grep 'manual' | tail -n1 | egrep -o '@.+' )";
|
||||||
@@ -317,6 +331,7 @@ for loc_ds bak_ds in ${(kv)work_datasets}; do
|
|||||||
last_loc_snaps[$loc_ds]=$loc_snap
|
last_loc_snaps[$loc_ds]=$loc_snap
|
||||||
else
|
else
|
||||||
if [[ $incremental = true ]]; then
|
if [[ $incremental = true ]]; then
|
||||||
|
printf $column_widths "${loc_ds}" "Не имеет снимков. Не возможно сделать инкрементный резерв."
|
||||||
unset "work_datasets[$loc_ds]"
|
unset "work_datasets[$loc_ds]"
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
@@ -325,25 +340,74 @@ done
|
|||||||
|
|
||||||
# --------- Запрашиваем подтверждение пользователя ----------
|
# --------- Запрашиваем подтверждение пользователя ----------
|
||||||
|
|
||||||
echo ""
|
estimate_changes_bytes() { # Рассчитать объём резервируемого пространства
|
||||||
echo "Список резервируемых датасетов:"
|
local loc_ds="$1"
|
||||||
column_widths="%-4s %-35s %-20s -> %-35s\n"
|
local base_snap="$2"
|
||||||
|
local value
|
||||||
|
if [[ -z "$base_snap" ]]; then
|
||||||
|
# Полный бэкап → used в байтах
|
||||||
|
value=$(zfs get -H -p -o value used "$loc_ds" 2>/dev/null)
|
||||||
|
else
|
||||||
|
# Инкремент → written@ в байтах
|
||||||
|
value=$(zfs get -H -p -o value "written@$base_snap" "$loc_ds" 2>/dev/null)
|
||||||
|
fi
|
||||||
|
# Если значение получено — выводим его, иначе — ничего (пусто)
|
||||||
|
if [[ -n "$value" && "$value" != "-" ]]; then
|
||||||
|
echo "$value"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
human_size() { # Перевести байты в читаемый вид
|
||||||
|
bytes="$1"
|
||||||
|
if [[ -z "$bytes" ]]; then
|
||||||
|
echo "N/A"
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
local one_gib=1073741824
|
||||||
|
if (( bytes >= one_gib )); then
|
||||||
|
numfmt --to=iec --format="%.1f" "$bytes" 2>/dev/null || echo "${bytes}B"
|
||||||
|
else
|
||||||
|
numfmt --to=iec --format="%.0f" "$bytes" 2>/dev/null || echo "${bytes}B"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
declare -A est_changes # Словарь хранения необходимого объёма для резервирования. Ключ - датасет, значнеие - объём в байтах
|
||||||
|
|
||||||
|
echo
|
||||||
|
echo "Список резервируемых датасетов:"
|
||||||
|
column_widths="%-4s %-35s %-20s %-10s -> %-35s\n"
|
||||||
|
printf "$column_widths" "#" "Локальный датасет" "Базовый снапшот" "Изменения" "Бэкап-датасет"
|
||||||
|
total_bytes=0
|
||||||
if [[ $incremental = true ]]; then
|
if [[ $incremental = true ]]; then
|
||||||
i=0; for loc_ds bak_ds in ${(kv)work_datasets}; do
|
i=0; for loc_ds bak_ds in ${(kv)work_datasets}; do
|
||||||
(( i++ ))
|
(( i++ ))
|
||||||
printf $column_widths $i $loc_ds "${last_loc_snaps[$loc_ds]}" $bak_ds
|
est_changes[$loc_ds]=$(estimate_changes_bytes "$loc_ds" "${last_loc_snaps[$loc_ds]}")
|
||||||
|
(( total_bytes += est_changes[$loc_ds] ))
|
||||||
|
printf $column_widths $i $loc_ds "${last_loc_snaps[$loc_ds]}" $(human_size "${est_changes[$loc_ds]}") $bak_ds
|
||||||
done
|
done
|
||||||
printf "%-4s %-35s %-20s\n" "" "Инкрементный снимок:" "$inc_snapshot (*)"
|
printf "%-4s %-35s %-20s\n" "" "Инкрементный снимок:" "$inc_snapshot (*)"
|
||||||
else
|
else
|
||||||
i=0; for loc_ds bak_ds in ${(kv)work_datasets}; do
|
i=0; for loc_ds bak_ds in ${(kv)work_datasets}; do
|
||||||
(( i++ ))
|
(( i++ ))
|
||||||
printf $column_widths $i $loc_ds "$inc_snapshot (*)" "$bak_ds (*)"
|
est_changes[$loc_ds]=$(estimate_changes_bytes "$loc_ds" "")
|
||||||
|
(( total_bytes += est_changes[$loc_ds] ))
|
||||||
|
printf $column_widths $i $loc_ds "$inc_snapshot (*)" $(human_size "${est_changes[$loc_ds]}") "$bak_ds (*)"
|
||||||
done
|
done
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# --------- Выбор датасетов ----------
|
# Итоговая сумма
|
||||||
|
echo
|
||||||
|
echo "Общий объём изменений: $(human_size $total_bytes)"
|
||||||
|
echo
|
||||||
|
echo "Свободное место на пулах бэкап-сервера:"
|
||||||
|
# Сортируем по имени пула (опционально)
|
||||||
|
for pool in ${(ko)backup_pool_free}; do
|
||||||
|
printf " %-20s : %s\n" "$pool" "${backup_pool_free[$pool]}"
|
||||||
|
done
|
||||||
|
echo
|
||||||
|
|
||||||
|
|
||||||
|
# --------- Выбор датасетов ----------
|
||||||
|
total_bytes=0
|
||||||
if [[ $check = true ]]; then
|
if [[ $check = true ]]; then
|
||||||
read 'work?Утвердите данные. "y" в работу | "1 2.." выбрать через пробел | НЕТ по умолчанию : '
|
read 'work?Утвердите данные. "y" в работу | "1 2.." выбрать через пробел | НЕТ по умолчанию : '
|
||||||
if [[ $work = *[[:digit:]]* ]]; then
|
if [[ $work = *[[:digit:]]* ]]; then
|
||||||
@@ -354,13 +418,19 @@ if [[ $check = true ]]; then
|
|||||||
if [[ ${indx_list[(r)$i]} != $i ]]; then # Если нет $i в списке $indx_list
|
if [[ ${indx_list[(r)$i]} != $i ]]; then # Если нет $i в списке $indx_list
|
||||||
unset "work_datasets[$loc_ds]"
|
unset "work_datasets[$loc_ds]"
|
||||||
else
|
else
|
||||||
echo "$i - $loc_ds \t ${last_loc_snaps[$loc_ds]} \t -> \t${bak_ds}"
|
(( total_bytes = est_changes[$loc_ds]))
|
||||||
|
printf $column_widths $i $loc_ds "${last_loc_snaps[$loc_ds]}" $(human_size "${est_changes[$loc_ds]}") $bak_ds
|
||||||
fi
|
fi
|
||||||
done
|
done
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Если был выведен список, то запросить запуск в работу нового списка
|
# Если был выведен список, то запросить запуск в работу нового списка
|
||||||
if [[ ${#indx_list} > 0 ]]; then
|
if [[ ${#indx_list} > 0 ]]; then
|
||||||
|
# Итоговая сумма
|
||||||
|
total_human=$(numfmt --to=iec --format="%.2f" "$total_bytes" 2>/dev/null || echo "${total_bytes}B")
|
||||||
|
echo
|
||||||
|
echo "Общий объём изменений: $total_human"
|
||||||
|
echo
|
||||||
work="N"
|
work="N"
|
||||||
read "work?В работу (y/N) "
|
read "work?В работу (y/N) "
|
||||||
fi
|
fi
|
||||||
@@ -419,7 +489,7 @@ fi
|
|||||||
# Вывод прогресса
|
# Вывод прогресса
|
||||||
# Это отдельный блок программы, который не имеет прямого доступа к переменным процесса в фоне.
|
# Это отдельный блок программы, который не имеет прямого доступа к переменным процесса в фоне.
|
||||||
# Обмен переменными проивится ерез временный файл
|
# Обмен переменными проивится ерез временный файл
|
||||||
if [[ $progress == true ]]; then
|
if [[ $progress == true ]] && { (( ! ${+work} )) || [[ $work =~ ^[yY]$ ]]; }; then
|
||||||
echo ""
|
echo ""
|
||||||
|
|
||||||
pids=$(ps -t $(tty | sed 's|/dev/||') -o pid,args | grep -e zfs_send.zsh -e "zfs send" | grep -v -- 'grep' | grep -v -- 'progress-only' ) # Список PIDs с командой запуска
|
pids=$(ps -t $(tty | sed 's|/dev/||') -o pid,args | grep -e zfs_send.zsh -e "zfs send" | grep -v -- 'grep' | grep -v -- 'progress-only' ) # Список PIDs с командой запуска
|
||||||
@@ -431,6 +501,7 @@ if [[ $progress == true ]]; then
|
|||||||
pids=$(ps a -o pid,args | grep -e zfs_send.zsh -e "zfs send" | grep -v -- 'grep' | grep -v -- 'progress-only' )
|
pids=$(ps a -o pid,args | grep -e zfs_send.zsh -e "zfs send" | grep -v -- 'grep' | grep -v -- 'progress-only' )
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
echo $pids
|
echo $pids
|
||||||
# Выводить прогресс запрашивая список запущенных процессов
|
# Выводить прогресс запрашивая список запущенных процессов
|
||||||
if [[ $pids = "" ]]; then
|
if [[ $pids = "" ]]; then
|
||||||
@@ -451,8 +522,8 @@ echo $pids
|
|||||||
time_changed_percent_value=$TS1
|
time_changed_percent_value=$TS1
|
||||||
|
|
||||||
n_tasks=$(< /dev/shm/backup_n_tasks)
|
n_tasks=$(< /dev/shm/backup_n_tasks)
|
||||||
|
while [[ $i_task -lt $n_tasks ]]; do # выводим статус пока не завершены все задачи
|
||||||
i_task=$(< /dev/shm/backup_i_task)
|
i_task=$(< /dev/shm/backup_i_task)
|
||||||
while [[ $i_task != $n_tasks ]]; do # выводим статус пока не завершены все задачи
|
|
||||||
progress=$(ps -u | grep "sending" | grep -v "grep" | sed -r "s/(.*) zfs: (.*)/\2/")
|
progress=$(ps -u | grep "sending" | grep -v "grep" | sed -r "s/(.*) zfs: (.*)/\2/")
|
||||||
|
|
||||||
# Извлекаем объем и процент
|
# Извлекаем объем и процент
|
||||||
@@ -539,8 +610,15 @@ echo $pids
|
|||||||
elapsed_total=$(( $(date +%s) - TS1 ))
|
elapsed_total=$(( $(date +%s) - TS1 ))
|
||||||
fi
|
fi
|
||||||
time_part_of_percent=$(( now - time_changed_percent_value )) # Время с последнего изменения процента
|
time_part_of_percent=$(( now - time_changed_percent_value )) # Время с последнего изменения процента
|
||||||
estimated_total=$(( 100 / percent * elapsed_total )) # Всего времени на задачу
|
# Используем awk для более точных расчётов
|
||||||
estimated_remain=$(( estimated_total - elapsed_total - time_part_of_percent )) # Осталось времени на задачу
|
# estimated_total - Всего времени на задачу
|
||||||
|
# estimated_remain - Осталось времени на задачу
|
||||||
|
read estimated_total estimated_remain <<< $(awk -v p="$percent" -v t="$elapsed_total" -v tp="$time_part_of_percent" '
|
||||||
|
BEGIN {
|
||||||
|
total = (p > 0 ? (100 / p * t) : 0);
|
||||||
|
remain = (total > 0 ? (total - t - tp) : 0);
|
||||||
|
printf("%d %d", total, remain)
|
||||||
|
}')
|
||||||
if (( estimated_remain >= 86400 )) || (( estimated_total >= 86400 )); then # Если осталось более чем сутки, то отобразить дни
|
if (( estimated_remain >= 86400 )) || (( estimated_total >= 86400 )); then # Если осталось более чем сутки, то отобразить дни
|
||||||
estimated_remain_days=$(( estimated_remain / 86400 )); estimated_remain_time=$(( estimated_remain % 86400 ))
|
estimated_remain_days=$(( estimated_remain / 86400 )); estimated_remain_time=$(( estimated_remain % 86400 ))
|
||||||
estimated_total_days=$(( estimated_total / 86400 )); estimated_total_time=$(( estimated_total % 86400 ))
|
estimated_total_days=$(( estimated_total / 86400 )); estimated_total_time=$(( estimated_total % 86400 ))
|
||||||
@@ -567,6 +645,8 @@ echo $pids
|
|||||||
[ -f /dev/shm/backup_i_task ] && rm /dev/shm/backup_i_task
|
[ -f /dev/shm/backup_i_task ] && rm /dev/shm/backup_i_task
|
||||||
[ -f /dev/shm//dev/shm/backup_time_start ] && rm /dev/shm/backup_time_start
|
[ -f /dev/shm//dev/shm/backup_time_start ] && rm /dev/shm/backup_time_start
|
||||||
[ -f /dev/shm//dev/shm/backup_time_circle ] && rm /dev/shm/backup_time_circle
|
[ -f /dev/shm//dev/shm/backup_time_circle ] && rm /dev/shm/backup_time_circle
|
||||||
|
# Обнуляем переменные после завершения задачи, чтобы избежать ошибок при повторном запуске
|
||||||
|
unset last_volume last_volume_time last_update_time last_percent time_changed_percent_value
|
||||||
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user