Raspberry, ZoneMinder, WebMin
В 2017-ом году возникла задача создания видео-наблюдения для одной камеры.
Решена задача на основе микро-компьютера «Raspberry Pi 3», usb-диска для ноутбука и системы ZoneMinder.
Но разговор далее пойдет не столько про ZoneMinder, сколько про «организацию» всего этого с максимально возможной надежностью.
Подобная «организация» была применена и на других малинках для решения других задач.
В интернете есть достаточно много информации про Raspberry, ZoneMinder и WebMin. Все, что здесь написано, получено из разных источников в интернете. Детально расписывать команды я не буду.
Что использовалось:
Raspberry Pi 3
microSD на 4 Гига
usb-диск на 640 Гиг
блок питания на 5В
microSD
Делаем загрузочную microSD на 4 Гига. На самом деле на ней нужен только ее первый раздел «boot», а все остальное будет «жить» на usb-диске. Но оставляем возможноть грузиться и с microSD в минимальном виде без ZoneMinder-а и WebMin-на. Т.е. после загрузки с microSD нужно доустановить пакеты типа «mc», через «raspi-config» настроить сеть, локаль, расширить файловую систему и т.д.
Разделы на microSD:
Device Boot Start End Sectors Size Id Type
/dev/mmcblk0p1 8192 532479 524288 256M c W95 FAT32 (LBA)
/dev/mmcblk0p2 532480 7661567 7129088 3.4G 83 Linux
Так как microSD одного объема реально имеют немного разные объемы, для «переносимости» на другие microSD нужно немного уменьшить второй раздел примерно на 20-30 Мег. Это можно сделать с помощью SystemRescueCd.
Подключаем usb-диск и грузимся с microSD.
Разделы на usb-диске
Создаем разделы на usb-диске.
Device Boot Start End Sectors Size Id Type
/dev/sda1 2048 67110911 67108864 32G 7 HPFS/NTFS/exFAT
/dev/sda2 67110912 943218687 876107776 417.8G 83 Linux
/dev/sda3 976773120 993550335 16777216 8G 83 Linux
/dev/sda4 993550336 1010327551 16777216 8G 83 Linux
sda1 - ntfs, для записи backup-ов microSD и восстановление microSD из dd-образов
sda2 - ext4, для данных (mysql, zoneminder, BackUp)
sda3 и sda4 - ext4, основной и резервные разделы для Raspbian (с установленными ZoneMinder и WebMin)
Небольшое пояснение. Собственно раздел sda1 (ntfs) нужен для обеспечения возможности «ручного» восстановления microSD на компьютере с ОС Windows, например с помощью Rufus.
С таким разбиением usb-диска имеем три варианта загрузки: с microSD и два с sda.
Немого про загрузку.
При подаче напряжения на малинку загрузчик читает всякую всячину из первого раздела microSD (mmcblk0p1). На последнем этапе чтения первого раздела считывается файл «cmdline.txt». В зависимости от его содержания и происходит дальнейшая загрузка ОС:
console=tty1 root=PARTUUID=6c586e13-02 rootfstype=ext4 elevator=deadline fsck.repair=yes rootwait
На данный момент «6c586e13-02» - второй раздел microSD и он будет рутовым.
Содержимое именно этого раздела позже скопируем на раздел sda3 usb-диска!
Создаем варианты загрузки
# ls -l /dev/disk/by-partuuid/
total 0
lrwxrwxrwx 1 root root 15 Oct 21 11:51 6c586e13-01 -> ../../mmcblk0p1
lrwxrwxrwx 1 root root 15 Oct 21 22:16 6c586e13-02 -> ../../mmcblk0p2
lrwxrwxrwx 1 root root 10 Oct 21 22:16 a3469a5d-01 -> ../../sda1
lrwxrwxrwx 1 root root 10 Oct 21 22:16 a3469a5d-02 -> ../../sda2
lrwxrwxrwx 1 root root 10 Oct 21 22:16 a3469a5d-03 -> ../../sda3
lrwxrwxrwx 1 root root 10 Oct 21 22:16 a3469a5d-04 -> ../../sda4
Создаем в «/boot» (первый раздел microSD) три файла:
cmdline.txt.sd:
console=tty1 root=PARTUUID=6c586e13-02 rootfstype=ext4 elevator=deadline fsck.repair=yes rootwait
cmdline.txt.sda3:
console=tty1 root=PARTUUID=a3469a5d-03 rootfstype=ext4 elevator=deadline fsck.repair=yes rootwait rootdelay=5
cmdline.txt.sda4:
console=tty1 root=PARTUUID=a3469a5d-04 rootfstype=ext4 elevator=deadline fsck.repair=yes rootwait rootdelay=5
В последних двух файлах рутовыми разделами становятся разделы 3 и 4 usb-диска и включена задержка загрузки на 5 секунд.
В дальнейшем командой, подобной этой:
root@zm:/boot
# cp cmdline.txt.sda3 cmdline.txt
будем переключать варианты загрузки.
Копирование ОС на sda3
Здесь приведу просто команды (они понятны):
root@zm:/
# mkdir /mnt/ntfs
root@zm:/
# mkdir /mnt/data
root@zm:/
# mkdir /mnt/sda3
root@zm:/
# mkdir /mnt/sda4
root@zm:/
# mount /dev/sda3 /mnt/sda3
root@zm:/
# cp -dpRx / /mnt/sda3/
Копирование может занять достаточно много времени, ждемс-с.
Готовим загрузку с раздела sda3
proc /proc proc defaults 0 0
#PARTUUID=6c586e13-01 /boot vfat defaults 0 2
PARTUUID=6c586e13-01 /boot vfat ro,noatime 0 0
PARTUUID=a3469a5d-03 / ext4 defaults,noatime 0 1
PARTUUID=a3469a5d-04 /mnt/sda4 ext4 noatime,noauto 0 0
PARTUUID=a3469a5d-02 /mnt/data ext4 defaults,noexec,acl 0 2
#/mnt/data/mysql /var/lib/mysql none bind
#/mnt/data/zoneminder /var/cache/zoneminder none bind
PARTUUID=a3469a5d-01 /mnt/ntfs ntfs-3g uid=1000,gid=1000,locale=ru_RU.UTF-8,umask=007
Внимание!
«/boot» монтируется «только по чтению»!
Рутовый раздел здесь - третий раздел usb-диска (sda3).
Грузимся с sda3
root@zm:/boot
# cp cmdline.txt.sda3 cmdline.txt
root@zm:/boot
# sync
root@zm:/boot
# reboot
На данном этапе установка пакетов, их настройка и т.д. будут записываться только на третий раздел usb-диска (sda3)!
MySQL
Написанное ниже применимо и к MariaDB.
После установки MySQL надо остановить сервер MySQL (например так):
root@zm:/
# /etc/init.d/mysql stop
После этого переносим любым удобным способом (например, «mc») каталог «mysql» из «/var/lib/» в «/mnt/data/» с сохранением атрибутов!
В «mc» это «[x] Preserve attributes».
Таким образом, данные сервера MySQL будут писаться в каталог «/mnt/data/mysql» (раздел sda2 usb-диска).
proc /proc proc defaults 0 0
#PARTUUID=6c586e13-01 /boot vfat defaults 0 2
PARTUUID=6c586e13-01 /boot vfat ro,noatime 0 0
PARTUUID=a3469a5d-03 / ext4 defaults,noatime 0 1
PARTUUID=a3469a5d-04 /mnt/sda4 ext4 noatime,noauto 0 0
PARTUUID=a3469a5d-02 /mnt/data ext4 defaults,noexec,acl 0 2
/mnt/data/mysql /var/lib/mysql none bind
#/mnt/data/zoneminder /var/cache/zoneminder none bind
PARTUUID=a3469a5d-01 /mnt/ntfs ntfs-3g uid=1000,gid=1000,locale=ru_RU.UTF-8,umask=007
Запускаем:
root@zm:/
# mount /mnt/data/mysql
root@zm:/
# /etc/init.d/mysql start
ZoneMinder
Аналогичную операцию по переносу производим с данными ZoneMinder после его установки:
останавливаем zoneminder
переносим «zoneminder» из «/var/cache/» в «/mnt/data/»
убираем комментарий у строчки «#/mnt/data/zoneminder…» в «/etc/fstab»
монтируем «/mnt/data/zoneminder»
запускаем zoneminder
WebMin
А вот теперь самое интересное.
Страница «Custom Commands»:
Страница «Custom Commands»:
Некоторые пункты страницы «Custom Commands» работают самостоятельно, а некоторые через вызов bash-скриптов. Сктрипты находятся в «/home/pi/.scripts».
Пункты страницы «Custom Commands» харянтся в каталоге «/etc/webmin/custom» с менами подобными этим «1510403022.cmd» и «1510403022.html» (два файла на один пункт). Формат их достаточно простой. Приведу содержимое файлов «*.cmd», но в названии буду использовать названия пунктов из «Custom Commands»:
Backup SD (~ 5 dd + 8 gzip min.)
Backup SD (~ 5 dd + 8 gzip min.)
/home/pi/.scripts/BackupSD $gzip
Backup SD (~ 5 dd + 8 gzip min.)
root 0 0 0 1 0 0 0 -
gzip:9:echo yes; echo no|:0,0:create gzip
Вызывает скрипт «BackupSD» с передачей параметра.
Создает dd-образ microSD в каталоге «/mnt/ntfs/BackUp/sd» с именами подобными этому «zm_2019-10-19.sd» и, если передан парамеир «yes» сжимает этот образ в «/mnt/data/BackUp/sd» с именем «zm_2019-10-19.sd.gz».
/home/pi/.scripts/CheckSdOrImageSD $param
Check SD Or dd-image SD
root 0 0 0 1 0 0 0 -
param:9:echo "sd";find "/mnt/ntfs/BackUp/sd" -name *.sd -print | awk -F '/' '{print $NF;}' | sort -r|:0,0:sd or file
Вызывает скрипт «CheckSdOrImageSD»
и в зависимости от параметра проверяет саму microSD либо выбранный из списка ее dd-образ (список формируется автоматом).
cat /proc/cpuinfo
CPUinfo
* 0 0 0 1 0 0 0 -
Работает самостоятельно, ну и понятно для чего.
vcgencmd version
Firmware
* 0 0 0 1 0 0 0 -
Работает самостоятельно, ну и понятно для чего.
dpkg -l > /mnt/data/BackUp/Installed/zm_$(date +%F_%H.%M).installed
Installed to file
root 0 0 0 0 0 0 0 -
Работает самостоятельно, ну и понятно для чего.
mount | grep boot; mount -o remount,rw /dev/mmcblk0p1 /boot; mount | grep boot
Remount boot (rw)
root 0 0 0 1 0 0 0 -
Работает самостоятельно.
Внимание! Ранее я писал что первый раздел microSD («/boot») монтируется в режиме «только читать». Делается это для увеличения надежности работы. Но при обновлении пакетов ОС может возникнуть необходимость записи в этот раздел. Для этого и есть этот пункт. Перед обновлением пакетов ОС его надо вызвать и он перемонтирует первый раздел для чтения/записи.
Rsync Root To Sda (~ 20 min.)
Rsync Root To Sda (~ 20 min.)
/home/pi/.scripts/RsyncRootToSda
Rsync Root To Sda (~ 20 min.)
root 0 0 0 1 0 0 0 -
Вызывает скрипт «RsyncRootToSda» без параметров.
Важный скрипт! Работает только с разделами usb-диска!
Автоматичекси определяет рутовый раздел и синхронизирует с ним другой раздел usb-диска. Т.е. если рутовым разделом является sda3, то sda4 бодет синхронизирован с ним. И наоборот, sda3 синхронизируется если рутовый - sda4. В скрипте жетско «зашита» работа с sda3 и sda4 (править ручками при другой организации)!
Некоторые файлы и каталоги НЕ синхронизируются специально (например файл «fstab»)!
Перед какими-либо действиями, да и просто регулярно лучше делать синхронизацию!
smartctl -a /dev/sda > /mnt/data/BackUp/SMART/zm_$(date +%F_%H.%M).smart
S.M.A.R.T sda to file
root 0 0 0 0 0 0 0 -
Работает самостоятельно, ну и понятно для чего.
Switch Root Partition And Reboot
Switch Root Partition And Reboot
if [ "$partition" = "show" ]; then mount | grep " / " | awk '{print $1}'; else /home/pi/.scripts/SwitchRootPartition $partition ; fi
Switch Root Partition And Reboot
root 0 0 0 1 0 0 0 -
partition:9:echo "show";echo "sda3";echo "sda4";echo "sd"|:0,0:root partition
Вызывает скрипт «SwitchRootPartition» с параметрами.
А это и есть «переключалка» загрузки.
vcgencmd measure_temp
Temperature CPU
* 0 0 0 0 0 0 0 -
Работает самостоятельно, ну и понятно для чего.
for id in core sdram_c sdram_i sdram_p; do echo "$id: $(vcgencmd measure_volts $id)"; done
Volts
* 0 0 0 1 0 0 0 -
Работает самостоятельно, ну и понятно для чего.
Скрипты
Теперь сами скрипты.
#!/bin/bash
# подключение дополнительных файлов, порядок важен
includeFiles=("Settings" "Functions")
# определяю откуда запущен скрипт
dir_scripts=`dirname $0`
# подключаю файл
for includeFile in "${includeFiles[@]}"; do
if ! [ -f "${dir_scripts}/${includeFile}" ]; then
echo "Не найден подключаемый файл \"${dir_scripts}/${includeFile}\""
exit 0
fi
. "${dir_scripts}/${includeFile}"
done
# переменные
path_backup="/mnt/ntfs/BackUp/sd"
path_zip="/mnt/data/BackUp/sd"
backup_file="${prefix}_$(date +%F).sd"
# основной код
isUserRoot
is_runing
verifyDir "$path_backup"
if [ -f "${path_backup}/${backup_file}" ]; then
rm ${path_backup}/${backup_file}
fi
# проверяю свободное место для образа
size_free=$(df -k ${path_backup} | grep '/' | awk '{print $4}')
# перевожу в мбайты
let "size_free /= 1000"
size_sd=$(fdisk -l | grep "/dev/mmcblk0:" | awk '{print $5}')
# перевожу в мбайты
let "size_sd /= 1000000"
# добавляю 10%
let "size_sd *= 11"
let "size_sd /= 10"
if [ "$size_sd" -gt "$size_free" ]; then
echoExit "Мало свободного места для образа SD: $size_free мБ"
fi
checkPartition "/dev/mmcblk0p1"
checkPartition "/dev/mmcblk0p2"
echoTime "Создание dd-образа SD: ${path_backup}/${backup_file}..."
dd if=/dev/mmcblk0 of=${path_backup}/${backup_file} bs=1M
if [ $? -eq 0 ]; then
echoTime "Создание образа SD прошло успешно."
if [ "$1" = "yes" ]; then
if [ -d "$path_zip" ]; then
echoTime "Сжатие файла ${backup_file} в каталог: ${path_zip}..."
gzip -ck --fast ${path_backup}/${backup_file} > ${path_zip}/${backup_file}.gz
if [ $? ]; then
echoTime "Файл ${backup_file} сжат успешно."
else
echoError "Ошибка сжатия файла ${backup_file}: $?!"
# [ -f "${path_zip}/${backup_file}" ] && rm ${path_zip}/${backup_file}
fi
else
echoError "Нет каталога для сжатия файла ${backup_file}: ${path_zip}!"
fi
fi
else
echoTime "Ошибка backup-а SD: $?!"
fi
mount /dev/mmcblk0p1
#!/bin/bash
# подключение дополнительных файлов, порядок важен
includeFiles=("Functions")
# определяю откуда запущен скрипт
dir_scripts=`dirname $0`
# подключаю файл
for includeFile in "${includeFiles[@]}"; do
if ! [ -f "${dir_scripts}/${includeFile}" ]; then
echo "Не найден подключаемый файл \"${dir_scripts}/${includeFile}\""
exit 0
fi
. "${dir_scripts}/${includeFile}"
done
# переменные
path_backup="/mnt/ntfs/BackUp/sd"
# основной код
isUserRoot
is_runing
if [ "$1" == "sd" ]; then
echo "Проверка SD..."
checkPartition "/dev/mmcblk0p1"
mountPartition "/dev/mmcblk0p1"
checkPartition "/dev/mmcblk0p2"
exit
fi
if ! [ -f "${path_backup}/${1}" ]; then
echoExit "Не найден файл \"${path_backup}/${1}\""
fi
echoTime "Проверка dd-образа: $1"...
l_device=$(losetup -all | grep "${1}" | awk '{print $1}')
if [ -z "$l_device" ]; then
l_device=$(losetup --find --partscan --show "${path_backup}/${1}")
if ! [ $? -eq 0 ]; then
echoExit "Ошибка loop-монтирования ${1}: $?!"
fi
fi
checkPartition ${l_device}p1
checkPartition ${l_device}p2
losetup --detach "$l_device"
# Функции для скриптов
# Тело функции должно заканчиваться точкой с запятой или символом новой строки
function echoExit {
echo "$1"
echo "Скрипт завершил свою работу!"
exit 0
}
function echoError {
echo "$1"
}
function echoTime {
echo " "
if [ -n "$1" ]; then
echo "$(date +%H:%M:%S) $1"
echo "========"
fi
}
function is_runing {
name="${0##*/}"
if [ $(pgrep -c $name) -gt 1 ]; then
echo "Скрипт ($name) уже запущен!"
echo "Повторный запуск этого скрипта возможен только после завершения предыдущего запуска!"
echo "Дождитесь завершения или прервите работу предыдущего запуска!"
echo " "
exit 0
fi
}
function isUserRoot {
if [ $(whoami) != "root" ]; then
echoExit "Запуск только под рутом!"
fi
}
function verifyDir {
if ! [ -d "$1" ]; then
mkdir -p "$1"
if ! [ $? ]; then
echoExit "Ошибка создания каталога ${1}: $?"
fi
fi
}
function mountPartition {
if [ -z "$(mount | grep "$1")" ]; then
mount $1
if ! [ $? -eq 0 ]; then
echoExit "Ошибка монтирования $1: $?!"
fi
fi
}
function unmountPartition {
if ! [ -z "$(mount | grep "$1")" ]; then
umount $1
if ! [ $? -eq 0 ]; then
echoExit "Ошибка отмонтирования $1: $?!"
fi
fi
}
function checkPartition {
if [ -n "$1" ]; then
unmountPartition "$1"
echoTime "Быстрая проверка файловой системы на $1..."
fsck -yf $1
if ! [ $? -eq 0 ]; then
echoExit "Ошибки файловой системы на $1: $?!"
fi
fi
}
#!/bin/bash
# подключение дополнительных файлов, порядок важен
includeFiles=("Functions")
# определяю откуда запущен скрипт
dir_scripts=`dirname $0`
# подключаю файл
for includeFile in "${includeFiles[@]}"; do
if ! [ -f "${dir_scripts}/${includeFile}" ]; then
echo "Не найден подключаемый файл \"${dir_scripts}/${includeFile}\""
exit 0
fi
. "${dir_scripts}/${includeFile}"
done
# основной код
isUserRoot
is_runing
rsync_to=""
rsync_from=$(mount | grep " / " | awk '{print $1}')
rsync_exclude=("/lost+found" "/tmp" "fstab" "swap" "/var/tmp")
case "$rsync_from" in
"/dev/sda3")
rsync_to="/dev/sda4"
;;
"/dev/sda4")
rsync_to="/dev/sda3"
;;
esac
if [ -z "$rsync_to" ]; then
echoExit "В настоящий момент малинка загружена с неизвестного раздела: ${rsync_from}!"
else
rsync_to=$(fdisk -l | grep "${rsync_to}" | awk '{print $1}')
fi
if [ -z "$rsync_to" ]; then
echoExit "На usb-диске не найден раздел для синхронизации!"
fi
checkPartition "$rsync_to"
mountPartition "$rsync_to"
exclude_str=""
echoTime "Исключения синхронизации:"
for exclude in "${rsync_exclude[@]}"; do
echo "$exclude"
exclude_str=${exclude_str}" --exclude "${exclude}
done
sync
echoTime "Синхронизация ${rsync_to} с рутовым разделом..."
rsync_to=$(mount | grep "$rsync_to" | awk '{print $3}')
## -n, --dry-run perform a trial run with no changes made
rsync -avchx --delete-during ${exclude_str} / ${rsync_to}/
if [ $? -eq 0 ]; then
sync
echoTime "Синхронизация прошла успешно."
unmountPartition "$rsync_to"
else
echoError "Ошибка синхронизации: $?!"
fi
# различные значения, индивидуалные для каждой малинки
prefix="zm"
#!/bin/bash
# подключение дополнительных файлов, порядок важен
includeFiles=("Functions")
# определяю откуда запущен скрипт
dir_scripts=`dirname $0`
# подключаю файл
for includeFile in "${includeFiles[@]}"; do
if ! [ -f "${dir_scripts}/${includeFile}" ]; then
echo "Не найден подключаемый файл \"${dir_scripts}/${includeFile}\""
exit 0
fi
. "${dir_scripts}/${includeFile}"
done
# основной код
isUserRoot
is_runing
root_partition=$(mount | grep " / " | awk '{print $1}')
if [ "$root_partition" == "/dev/$1" ]; then
echoExit "Текущий рутовый раздел совпадает с выбранным."
fi
if [ -z "$(mount | grep "/boot")" ]; then
echoExit "Раздел boot не примонтирован!"
fi
if ! [ -f "/boot/cmdline.txt.$1" ]; then
echoExit "Не найден файл: /boot/cmdline.txt.$1"
fi
if [ "$1" == "sd" ]; then
checkPartition "/dev/mmcblk0p2"
else
checkPartition "/dev/$1"
fi
mount -o remount,rw /dev/mmcblk0p1 /boot
if ! [ $? -eq 0 ]; then
echoExit "Ошибка перемонтирования раздела /boot!"
fi
cp "/boot/cmdline.txt.$1" "/boot/cmdline.txt"
if ! [ $? -eq 0 ]; then
echoExit "Ошибка копирования файла /boot/cmdline.txt.$1 в файл /boot/cmdline.txt"
fi
echo " "
sync
echo "Рутовый раздел изменен на /dev/$1."
echo "Перезагрузка через 3 секунды!"
sleep 3
reboot
«Functions» и «Settings» - чисто вспомогалельные фпайлы и атрибут «исполняемый» им не нужен. Остальным нужно присвоить атрибут «исполняемый»!
В скриптах куча всяких проверок и выводом подробных сообщений в окно WebMin-а!
Есть еще один скрипт в каталоге «/root/.scripts»!
#!/bin/bash
# функции
function echoExit {
echo "$1"
echo "Скрипт завершил свою работу!"
exit 0
}
function echoTime {
echo " "
if [ -n "$1" ]; then
echo "$(date +%H:%M:%S) $1"
echo "========"
fi
}
function is_runing {
name="${0##*/}"
if [ $(pgrep -c $name) -gt 1 ]; then
echo "Скрипт ($name) уже запущен!"
echo "Повторный запуск этого скрипта возможен только после завершения предыдущего запуска!"
echo "Дождитесь завершения или прервите работу предыдущего запуска!"
echo " "
exit 0
fi
}
function mountPartition {
if [ -z "$(mount | grep "$1")" ]; then
mount $1
if ! [ $? -eq 0 ]; then
echoExit "Ошибка монтирования $1: $?!"
fi
fi
}
function unmountPartition {
if ! [ -z "$(mount | grep "$1")" ]; then
umount $1
if ! [ $? -eq 0 ]; then
echoExit "Ошибка отмонтирования $1: $?!"
fi
fi
}
function checkPartition {
if [ -n "$1" ]; then
unmountPartition "$1"
echoTime "Быстрая проверка файловой системы на $1..."
fsck -yf $1
if ! [ $? -eq 0 ]; then
echoExit "Ошибки файловой системы на $1: $?!"
fi
fi
}
function restoreSD {
if [ "$1" != "exit" ]; then
echo " "
echoTime "Восстановление SD из файла: $1"
unmountPartition "/dev/mmcblk0p1"
unmountPartition "/dev/mmcblk0p2"
dd if=${path_backup}/$1 of=/dev/mmcblk0 bs=1M
if [ $? -eq 0 ]; then
echoTime "Восстановление SD прошло успешно."
checkPartition "/dev/mmcblk0p1"
mountPartition "/dev/mmcblk0p1"
checkPartition "/dev/mmcblk0p2"
else
echoTime "Ошибка восстановления SD: $?!"
fi
fi
}
# переменные
path_backup="/mnt/ntfs/BackUp/sd"
# основной код
clear
echo " "
is_runing
echo "Восстановление SD (приблизительно 23 мин.)..."
#files=$(find "$path_backup" -name *.sd -print | awk -F '/' '{print $NF;}')
# в именах файлов *.sd НЕ должно быть пробелов!
# Если в каталоге нет ни одного файла, соответствующего шаблону,
# то за имя файла принимается сам шаблон.
# Чтобы избежать этого, используйте ключ nullglob
shopt -s nullglob
for file_sd in "${path_backup}/"*.sd; do
files_list=( "${files_list[@]}" "${file_sd##*/}" )
done
# убираю ключ nullglob
shopt -u nullglob
# добавка последним элементом, для возможности выбора выхода
files_list=( "${files_list[@]}" "exit" )
if [ ${#files_list[@]} -le 1 ]; then
echoExit "Не найдены файлы ${path_backup}/*.sd!"
fi
echo "Список найденных файлов:"
COLUMNS=12
PS3='Номер файла для восстановления:'
select file_restore in "${files_list[@]}"; do
restoreSD "$file_restore"
break
done
echoExit
Вот он и предназначен для восстановления microSD из dd-образа!
Работать с ним надо только в терминале и под рутом! И понятно, что малинка должна быть загружена с раздела usb-диска!
Подготовка sda4 для загрузки
Настало время для раздела sda4 usb-диска.
Напомню что малинка на данном этапе должна быть загружена с sda3 usb-диска!
Здесь тоже приведу просто команды (они понятны):
root@zm:/
# mount /dev/sda4 /mnt/sda4
root@zm:/
# cp -dpRx / /mnt/sda4/
Файл «/mnt/sda4/etc/fstab»:
Файл «/mnt/sda4/etc/fstab»:
proc /proc proc defaults 0 0
#PARTUUID=6c586e13-01 /boot vfat defaults 0 2
PARTUUID=6c586e13-01 /boot vfat ro,noatime 0 0
PARTUUID=a3469a5d-04 / ext4 defaults,noatime 0 1
PARTUUID=a3469a5d-03 /mnt/sda3 ext4 noatime,noauto 0 0
PARTUUID=a3469a5d-02 /mnt/data ext4 defaults,noexec,acl 0 2
/mnt/data/mysql /var/lib/mysql none bind
/mnt/data/zoneminder /var/cache/zoneminder none bind
PARTUUID=a3469a5d-01 /mnt/ntfs ntfs-3g uid=1000,gid=1000,locale=ru_RU.UTF-8,umask=007
Думаю понятны отличия от файла «/etc/fstab».
После этих действий можно переклюситься на загрузку с sda4 в WebMin-е.
Работа с полученной конфигурацией
Здесь собраны некоторые моменты и тонкости дальнейшей работы.
Обновление пакетов ОС
Это один из «болезненных» моментов работы с малинкой если обновления затрагивают загрузчик, firmware, драйвера и подобное!
Несколько пунктов кратко:
Малинка должна быть загружена с раздела usb-диска (например sda3)!
Перед обновлением нужно синхронизировать разделы sda usb-диска (пункт «Rsync Root To Sda (~ 20 min.)»)!
Перемонтировать первый раздел microSD (пункт «Remount boot (rw)»)!
Обновляем пакеты на usb-диске (рекомендую делать это в консоле).
Переключаемся на загрузку с microSD (пункт «Switch Root Partition And Reboot») и обновляем пакеты уже на ней (консоль).
Переключаемся обратно именно на sda3 (пункт «Switch Root Partition And Reboot»).
Делаем пункт «Backup SD (~ 5 dd + 8 gzip min.)»!
И если все хорошо, то делаем пункт «Rsync Root To Sda (~ 20 min.)»
Если что-то пошло не так, то есть dd-образ(ы) microSD («/mnt/ntfs/BackUp/sd») и предыдущая рабочая ОС на другом разделе usb-диска!
Такой способ «отката» использую даже в случае если пакет «криво» встал. Было такое с обновлением ZoneMinder-а. Тогда просто переключаемся на загрузку с раздел sda4, синхронизируем sda, и возвращаемся на загрузку с sda3.
Восстановление microSD
Здесь тоже есть некоторые тонкости.
Уже писал что одинаковые по объему флешки (microSD - тоже флешка) немного различаются по реальному объему. И писал что нужно немного уменьшить второй раздел флешки (SystemRescueCd).
При восстановлении флешки из dd-образа, dd может выдать что не хватает места. Если второй раздел все же поместился на флешку, то все нормально! А если нет, то тогда придется с помощью SystemRescueCd уменьшить второй раздел в самом dd-образе! И после этого опять «натравить» dd.
Столкнулся еще с одной «засадой» при переходе с «Raspbian Linux 9» на «Raspbian Linux 10»!
Причина «засады» в том, что в предыдущих образах Raspbian из интернета первый раздел в них был объемом около 50 Мег. В последующих образах объем уже был 256 Мег! И для обновление до «Raspbian Linux 10» банально не хватало места на первом разделе! SystemRescueCd и подобное ничем не помогли, увеличить первый раздел флешки (LBA) не получилось! Но это уже другая история ;))