Моё домашнее сетевое хранилище из raspberry pi 4b
Не уверен, что мой способ оптимальный по надёжности или расходам, но для истории запишу его здесь, включая особенности и подводные камни.
P.S. Время от времени пробую сделать с малинкой что-нибудь новое и дополняю статью.
Установка ОС
Есть готовая штука - raspberry pi imager, в которой можно выбрать желаемую ОС и залить её на sd-карту. В случае с малинкой на 8 гб оперативной памяти стоит обратить внимание на битность ОС. Якобы видеодрайвера в 64битной ОС работают плохо, но в дальнейшем мне они и не нужны. Я выбрал 64-битную версию Ubuntu server.
Можно установить ОС, не подключая монитор/клавиатуру/мышь. Чтобы она подключилась к сети, где-то в файлике на sd-карточке с ОС придётся прописать имя и пароль от wifi сети. Но ещё лучше (и на мой взгляд проще) подключить сеть через кабель к роутеру. В малинке гигабитный порт, для нужд домашнего хранилища самое то. Вышеописанные шаги с паролем не нужны, зато неплохо бы иметь доступ на роутер, чтобы узнать, по какому адресу доступна малинка.
Либо можно просканировать локальную сеть и так узнать адрес:
sudo nmap -sn 192.168.1.0/24
При самом первом запуске ОС грузится несколько минут, а я поначалу думал что что-то делаю не так. Вся дальнейшая работа и настройка делается через ssh
Внешний жёсткий диск
Я купил внешний жёсткий диск на 4Тб: HDD WD Elements Desktop WDBWLG0040HBK-EESN. Из особенностей - у него есть отдельный провод для питания от розетки и он не должен тащить энергию из малинки. Из минусов - в розетку придётся втыкать два девайса. У малинки синие разъёмы - usb 3.0, диск лучше втыкать в них.
Дальше начинаются приключения: raspberry pi imager при установке ОС размечает сд-карточку в MBR. Можно вместо карточки подключить жёсткий диск, но MBR позволит использовать только первые 2 Тб диска. А ещё хочется отказаться от sd карточки. Почему-то полностью переселить ОС на диск c GPT разметкой у меня не получилось, ОС не грузилась. В качестве полумеры я оставил загрузочный раздел на sd карточке и раздел с ОС перенёс на диск. Количество записей на карточку будет минимальным, и в случае потери я смогу вставить новую карточку памяти с загрузчиком и работать дальше.
Самое странное, что в сдругим жёстким диском toshiba на 2 гб с gpt внезапно загрузиться получилось. Я потратил около дня, но так и не понял, почему с одного диска грузится, а с другого - нет.
Можно взять раздел с ОС прямо с сд карточки (зря что ли на неё ОС ставили и настраивали) и скопировать на диск. Дальше понадобится узнать айдишники для разделов.
sudo blkid
Примерное описание тут: https://linuxtut.com/en/7af17f1272ca0c558d47/
Эти айдишники надо будет обновить в двух местах:
- В загрузчике boot/cmdline.txt указывает, откуда грузить ОС.
- В самой ОС в еtc/fstab монтируются и загрузчик, и раздел с ОС
Как я потом узнал - не обязательно использовать PARTUUID, можно вместо него указать PARTLABEL. Название раздела можно сделать любое, и мне это показалось более удобным. Но надо проследить, чтобы не было двух разделов с одинаковым названием.
В малинке целых 2 usb порта, поэтому можно включить рядышком второй диск и сделать софтовый RAID1. На двух дисках будут лежать идентичные копии данных, чтение теоретически может ускориться в два раза, запись будет со скоростью самого медленного и некоторыми приколами дисков с черепичной записью. К сожалению, о современных дисках производители эту информацию не особо публикуют, в одной "модели" может оказаться как одно, так и другое, и я RAID делать не стал. В планах организовать резервное копирование с домашнего ПК на малинку - тогда отказ одного из дисков или устройств не будет фатальным.
Применение
- sshfs для доступа из Линукса для тех, кто не осилил нормальную настройку самбы. (это я)
- Samba для доступа к папкам из локальной сети не только из линукса
- QBittorrent с возможностью управлять им из браузера
- 24/7 крутить своего телеграм бота. (это тоже я)
- Хранить git-репозитории
- Подключить второй массив и организовать raid
- Настроить резервное копирование с помощью rsync и cron
- Раздобыть вторую малинку и организовать загрузку по сети, вообще без sd- карты
- Добавить пассивное охлаждение
- Настроить LUKS шифрование для дисков
Скорость копирования по проводу через sshfs получилась 44-47 мегабайт в секунду. Почти полгигабита! У меня есть подозрение, что сеть на малинке быстрее, а это тормозит жёсткий диск или роутер, но пруфов не будет.
Автозапуск бота и торрентов
Ниже будет упрощённый пересказ того, что я узнал по этой ссылке: https://obu4alka.ru/ustanovka-qbittorrent-na-ubuntu-server-20-04-lts.html
Кстати, qbittorrent в браузере выглядит практически так же, как и приложение. И ещё он качает/раздаёт круглые сутки, так что можно оживить полузабытые раздачи.
В ubuntu есть systemd, которая запускает сервисы при старте ОС. Чтобы бот и торрент тоже запускались, сделаем текстовые файлики:
etc/systemd/system/qbittorrent-nox.service
[Unit] Description=qBittorrent Command Line Client After=network.target [Service] Type=forking User=qbittorrent-nox Group=qbittorrent-nox UMask=007 ExecStart=/usr/bin/qbittorrent-nox -d --webui-port=8080 Restart=on-failure [Install] WantedBy=multi-user.target
[Unit] Description = Runs my bot Wants=network-online.target After=network-online.target nss-lookup.target [Service] Type=simple ExecStart=/home/ubuntu/bot/run.sh [Install] WantedBy=multi-user.target
В случае qbittorrent я создал отдельного пользователя, но если честно не особо понял смысл. Ну да, так вроде безопаснее. А потом самба не cможет открыть файл, т.к. у неё нет прав на чтение и вас ждут красота и изучение того как в линуксе работают пользователи, группы и разрешения.
В настройках qbittorrent (прямо в веб-интерфейсе) есть смысл поменять дефолтную папку для хранения торрентов, а так же залезть во вкладку webUI и убрать там галочку "Use UPnP / NAT-PMP to forward the port from my router", чтобы запретить вход в веб-интерфейс из сети снаружи роутера. Ещё там есть опция "Bypass authentication for clients on localhost", её наоборот удобнее включить для доступа из локальной сети без пароля.
В windows 10 отключили старую версию протокола smb, который позволял заходить без авторизации. Но с самбой всё как-то криво складывается, я сделал readonly шару и забил, потому что из линукса достаточно sshfs.
Для того, чтобы самба переходила по символьным ссылкам, придётся ещё какие-то опции указывать. Их тут нет. /etc/samba/smb.conf
[share] path = /mnt/data/share/ read only = yes guest ok = yes guest only = yes
sshfs
Очень удобный вариант, можно удалённую папку примонтировать как папку у себя. Я даже alias завёл:
alias mount-pi8="sshfs -o reconnect,ServerAliveInterval=15,ServerAliveCountMax=3 [email protected]:/ /home/me/Desktop/pi8" alias umount-pi8="umount /home/me/Desktop/pi8"
Единственное - несмотря на параметры, оно как-то криво работает при покидании домашней сети. Ноутбук был не рад, пришлось перезагружать. По тем же причинам в автозапуск такое ставить не надо. Руками каждый раз монтировать - чуточку лень. Возможно, я всё-таки донастрою самбу или ещё что-то.
В остальном - шикарно, скорость работы близка к скорости жёсткого диска (возможно, не современного, а десятилетней давности, но всё же).
Хранение git-репозиториев
Я не стал использовать что-то типа gitlab и пошёл по максимально простому пути. В git сервер и клиент равноправны, на клиенте хранится своя копия данных. Между ними нет большой разницы, и я под сервером подразумеваю круглосуточно работающий ПК, и предполагаю что клиент запускается время от времени и закидывает на сервер свои изменения.
Адресов для скачивания/заливания репозитория может быть несколько. Обычно он один и называется origin. Но никто не запретит добавить несколько адресов, выкачать ветку с одного и потом её влить по другому адресу. И даже в совершенно другой репозиторий так можно, лишь бы имена веток не пересекались. Гибкость впечатляет, хоть про неё и не все знают.
Если есть ssh доступ к серверу, то добавить туда репозиторий очень просто:
На сервере: создать папку, в ней вызвать
git init --bare
git remote add pi8 ssh://user@ip/path-to-repo git push pi8 master
В идеале ещё можно создать отдельного пользователя для git и т.п., но для локального домашнего применения не вижу смысла.
Подробнее можно прочитать тут: https://git-scm.com/book/en/v2/Git-on-the-Server-Setting-Up-the-Server
RAID массив
В итоге я отказался от этой идеи.
С помощью mdadm можно сделать raid1 массив, в котором данные будут лежать в двух копиях. Одна на одном диске и другая на другом. Т.к. диски обычно неравных размеров, лучше вместо указания дисков (типа /dev/sda) использовать разделы: например /dev/sda1. Само собой, разделы должны быть на разных дисках. В случае отказа одного из дисков массив с данными продолжит работать как ни в чём ни бывало, пока не откажет второй диск. В общем, за статусом raid массива надо будет следить (и мне кажется, это неудобно).
Вдобавок, я сделал раздел с raid только для важных данных, и при отказе основного диска ОС похерится и малинка всё равно не запустится. Я подумал над этим безобразием и решил вместо этого сделать бэкапы.
Бэкапы
В отличие от raid массива, у бэкапов есть очень полезное свойство - при случайном удалении файлов их можно будет восстановить.
Я подумал над возможными вариантами:
- есть специальное ПО типа https://ru.wikipedia.org/wiki/Bacula. Инкрементальные бэкапы хранятся в каком-то бинарном формате, само ПО надо ставить и настраивать.
- rsync + cron. Внезапно, rsync умеет делать инкрементальные бэкапы. Чтобы не хранить один файл по многу раз, используюся жёсткие ссылки. С точки зрения меня как пользователя получатся папки типа backup1, backup2, и т.д, в каждой из которых лежат все файлы на момент копирования.
На мой взгляд, с rsync самый удобный вариант - я смогу вставить диск с бэкапами в любой комп и вытащить данные обратно.
Я вдохновлялся вот этой статьёй: https://linuxconfig.org/how-to-create-incremental-backups-using-rsync-on-linux, но сделал немножко по-другому:
readonly DATETIME="$(date '+%Y-%m-%d_%H:%M:%S')" readonly LOG_FILE="/mnt/backup/logs/backup_safedata_${DATETIME}.txt" printf "start at ${DATETIME}\n" >> $LOG_FILE rsync -av --delete /mnt/safedata --link-dest ../latest --exclude "lost+found" /mnt/backup/safedata/${DATETIME} >> $LOG_FILE df >> $LOG_FILE echo "finished at $(date '+%Y-%m-%d_%H:%M:%S')" >> $LOG_FILE cd /mnt/backup/safedata rm latest ln -s ${DATETIME} latest python3 -c ' import os import shutil listdir = os.listdir(".") files = sorted([f for f in listdir if f.startswith("20")]) to_preserve = set(files[-31:]) monthly = {f[:7]: f for f in reversed(files)} to_preserve.update(monthly.values()) for f in sorted(to_preserve): print(f"keep '{f}'") for f in set(files) - to_preserve: print(f"remove '{f}'") shutil.rmtree(f) ' >> $LOG_FILE
latest - мягкая ссылка на последний бэкап. В --link-dest путь должен быть абсолютный или относительный для папки с бэкапом, поэтому надо выскочить на директорию вверх: ../latest
если ссылки нет, то rsync всё ещё работает.
Логи я решил каждый раз писать в отдельный файл, так как иначе при добавлении большого количества файлов он превращается в многомегабайтный лог и в нём сложно найти, что в какой день происходило. Папка для логов сама не создастся, надо добавить её вручную
скрипт на питоне хранит последние 30 версий бэкапа и первую версию каждого месяца, остальные удаляет.
Для отладки есть полезные опции --dry-run
для запуска без реальных изменений, а так же можно опцию --verbose
указать дважды и получить более подробный вывод: -vv
Опция -a раскрывается в кучу настроек, которые пытаются перетащить время, пользователя файла, права доступа и т.п. Я попробовал запускать rsync между компом и малинкой и несмотря на --link-dest
создавались копии файлов. Я потратил кучу времени на то, чтобы понять, что все эти опции мешают и в итоге между машинами копировал с опциями -rl --size-only
В этом случае время игнорируется, а файл с тем же размером считается не требующим копирования. Можно ещё указать --checksum, но тогда и передающая и принимающая стороны будут считать md5 чексуммы для файлов, что потребует их полного прочтения. Я решил забить и ограничиться проверкой размера.
C помощью cron можно будет настроить бэкапы на каждый день или каждую неделю:
crontab -e
20 4 * * * /mnt/backup/backup.sh
Чтобы каждую ночь в 4:20 делать бэкап.
Загрузка по сети*
*Задание со звёздочкой, будут приключения.
Заливать образ на флешку, вставлять в малинку, ждать пока загрузится - не самое весёлое занятие. После того, как перевая малинка стала работающим 24/7 полноценным сервером, я раздобыл вторую и решил попробовать загрузку по сети.
Глобально это состоит из двух шагов:
Из принципиальных ограничений - клиентская малинка должна быть подключена к сети по проводу, а не через wifi.
Список команд и т.п. несложный, но в процессе я нашёл кучу подводных камней, и настройка заняла у меня целый вечер.
https://www.raspberrypi.com/documentation/computers/remote-access.html#network-boot-your-raspberry-pi
Вот ссылка на документацию, но делать надо далеко не всё, что в ней написано.
Настройка клиента
На 4B, загрузку включать надо, причём процесс отличается от предыдущих версий.
Во-первых, понадобится поставить именно raspbian os. В принципе, можно обойтись и без подключения мышки-клавиатуры, если в rpi Imager нажать ctrl+shift+x (или какое-то ещё неочевидное сочетание клавиш), оказаться в настройках и там указать ключ для ssh. Экран я советую всё-таки подключить, он потом пригодится.
Запускаем штуку для настройки (прям из консоли)
sudo raspi-config
В ней в менюшках надо выбрать Advanced Options
, Boot Order
, Network Boot
и в ней выбрать загрузку по сети (что странно - с загрузкой по сети вариант ровно один - малинка попытается загрузиться с sd карты, если не получится - то по сети).
Чтобы изменения применились, придётся перезагрузиться.
vcgencmd bootloader_config
можно посмотреть, что в BOOT_ORDER. В идеале там должно быть 0xf21. Тут можно посмотреть подробнее, что же число значит:
https://www.raspberrypi.com/documentation/computers/raspberry-pi.html#raspberry-pi-4-bootloader-configuration
Я так понял, они справа налево идут.
После этого малинку можно выключить, выдернуть из неё sd-карту и снова включить. Выше я советовал включить экран - малинка на нём покажет текстом свой ip адрес и потихоньку растущее количество неудачных попыток загрузки по сети.
Настройка сервера
Дальше инструкции с официального сайта следовать не обязательно.
Во-первых, можно просто взять и на на домашнем роутере во вкладочке с настройками DHCP указать статические ip адреса для сервера (давно уже так сделал) и для клиента (сделал сейчас).
Во вторых, у меня клиент - raspbian os, а сервер ubuntu. На сайте происходит кунг-фу по включению той же самой sd-карты в сервер, загрузке с неё и дальнейшей модификации для того, чтобы клиент и сервер различались. Спасибо, но я воздержусь.
Итак, можно смело листать сайт до инструкции
sudo apt install tcpdump dnsmasq sudo systemctl enable dnsmasq sudo tcpdump -i eth0 port bootpc
Но dnsmasq ставить пока не надо.
С помощью tcpdump можно убедиться, что малинка при загрузке действительно посылает запросы всем в локальной сети.
С dnsmasq меня ждал сюрприз. Это в одном лице и dns сервер, и нужные нам dhcp и tftp сервера. В ubuntu уже есть свой dns-резолвер, systemd-resolved, и он, как нетрудно догадаться, тоже dns сервер, который слушает порт 53 и не даёт слушать его другим.
После установки dnsmasq полезет на 53 порт и не сможет - тот уже занят. Всё что надо сделать - залезть в /etc/dnsmasq.conf и указать там port=0, чтобы dns-составляющая не запускалась и не мешалась.
https://askubuntu.com/questions/191226/dnsmasq-failed-to-create-listening-socket-for-port-53-address-already-in-use
Я вместо этого воспользовался первым нагугленным советом и отключил systemd-resolved. В итоге dns работать перестал, я узнал, что команде sudo без dns работается плохо и потом возвращал всё обратно.
так вот, в dnsmasq.conf должно в итоге быть
port=0 dhcp-range=192.168.1.255,proxy log-dhcp enable-tftp tftp-root=/tftpboot pxe-service=0,"Raspberry Pi Boot"
tftp-root - путь к папке с загрузчиком на сервере
как скопировать системный раздел на сервер
В оригинальной статье они обходятся одной sd-картой, но я по понятным причинам включил sd-карту в ноутбук и задумался, как же скопировать её на сервер. Суть проблемы в том, что
rsync from to
не может скопировать файлы, принадлежащие root и запрещённые другим для чтения. А поскольку мы копируем системный раздел, там такие файлы будут.
sudo rsync from to
Тогда получится скопировать файлы с владельцем root, но не получится их записать по двум причинам:
- у рута своя папка .ssh и там может не быть ключа для ssh
- на системе, куда копируем, rsync работает не из под-рута
Красота и удобство. Указать root@ip:/path у меня тоже не получилось. Первая проблема решается копированием ~/.ssh/id_rsa, вторая - хитрой опцией. Для хитрой опции требуется, чтобы на другой машине команда sudo не спрашивала пароль. Рабочая команда выглядит так:
sudo rsync --progress -ax -e "ssh" --rsync-path="sudo rsync" rootfs/ [email protected]:/nfs/client1
Таким незамысловатым образом можно скопировать загрузчик в /nfs/nfsboot, а системный раздел в /nfs/client1
Идею и команду взял отсюда: https://superuser.com/questions/605425/rsync-root-files-between-systems-without-specifying-password
После этого надо будет в nfsboot/cmdline.txt заменить указание на root: у меня получилось так:
console=serial0,115200 console=tty1 root=/dev/nfs nfsroot=192.168.1.142:/nfs/client1,vers=4.1,proto=tcp rw ip=dhcp rootwait
192.168.1.142:/nfs/tftpboot /boot nfs defaults,vers=4.1,proto=tcp 0 0
То, что есть только boot без корневой системы - нормально, так и должно быть
Кроме того, надо ещё включить rpcbind, настроить nfs-kernel-server и в /etc/exports указать пути для nfs:
/nfs/client1 *(rw,sync,no_subtree_check,no_root_squash) /nfs/tftpboot *(rw,sync,no_subtree_check,no_root_squash)
В результате малинка без SD-карты загружается по сети из файлов в папке. При желании ту папку можно будет копировать, хранить несколько разных версий ОС и т.п.
Пассивное охлаждение
vcgencmd measure_temp
У меня она была 64 градуса (с нагрузкой из описанного выше софта), после - 57. Я купил корпус Qumo Aluminum RS001, который заодно является радиатором. Охлаждение пассивное, вентилятора нет и ломаться нечему. И шуметь тоже. Сам корпус, кажется, сделан из аллюминия. Смотрится красиво и солидно. В комплекте шли два маленьких кусочка термопрокладки, болтики и шестигранный ключик - в общём, всё необходимое.
Эффектом немного разочарован, думал будет градусов 10-15 разницы, а получилось около 7. Сам корпус ощутимо горячий во всех местах
LUKS шифрование раздела
Опциональная часть, надо чётко понимать, нужно ли шифрование. Цель - чтобы содержимое диска было невозможно прочитать при включении в посторонний пк.
Если пароль от раздела будет храниться где-то на малинке, то затея бессмысленная. Я планирую заходить на малинку по ssh и руками подключать зашифрованный раздел. Кроме того - для самой малинки, пока она работает, пароль известен. Если сама малинка скомпроментирована - то злоумышленник и имеет доступ к диску, и может утащить к себе пароль.
Шифрование спасает только от сценария, когда постороний забирает диск и пытается прочитать с него данные. Всё, ничего больше.
Я где-то видел, чтобы на малине как-то хитро шифровали прям диск с системой, но я так извращаться не буду. В малинке негде хранить пароль, и я хочу, чтобы она могла сама включиться без моего участия. Шифрую только отдельный раздел с данными.
Итак, если шифрование всё-таки нужно, начнём:
Скорее всего, cryptsetup уже установлен, если нет, ставим:
sudo apt install cryptsetup
Особенности малинки - нет хардварной поддержки aes, скорость шифровки и дешифровки будет медленная, советуют вместо него использовать adiantum.
Проверить скорость шифрования (без записи на диск, всё в RAM):
cryptsetup benchmark # Tests are approximate using memory only (no storage IO). PBKDF2-sha1 386643 iterations per second for 256-bit key PBKDF2-sha256 630912 iterations per second for 256-bit key PBKDF2-sha512 510007 iterations per second for 256-bit key PBKDF2-ripemd160 324034 iterations per second for 256-bit key PBKDF2-whirlpool 142935 iterations per second for 256-bit key argon2i 4 iterations, 333516 memory, 4 parallel threads (CPUs) for 256-bit key (requested 2000 ms time) argon2id 4 iterations, 335222 memory, 4 parallel threads (CPUs) for 256-bit key (requested 2000 ms time) # Algorithm | Key | Encryption | Decryption aes-cbc 128b 85.6 MiB/s 98.5 MiB/s serpent-cbc 128b 42.7 MiB/s 44.4 MiB/s twofish-cbc 128b 63.3 MiB/s 69.1 MiB/s aes-cbc 256b 76.0 MiB/s 77.5 MiB/s serpent-cbc 256b 43.8 MiB/s 44.4 MiB/s twofish-cbc 256b 68.1 MiB/s 69.2 MiB/s aes-xts 256b 84.4 MiB/s 101.8 MiB/s serpent-xts 256b 43.1 MiB/s 44.8 MiB/s twofish-xts 256b 67.9 MiB/s 70.7 MiB/s aes-xts 512b 78.6 MiB/s 79.8 MiB/s serpent-xts 512b 44.9 MiB/s 44.7 MiB/s twofish-xts 512b 70.5 MiB/s 70.6 MiB/s
И померять aes-adiantum, который должен быть быстрее
cryptsetup benchmark -c xchacha12,aes-adiantum # Tests are approximate using memory only (no storage IO). # Algorithm | Key | Encryption | Decryption xchacha12,aes-adiantum 256b 188.1 MiB/s 189.2 MiB/s cryptsetup benchmark -c xchacha20,aes-adiantum # Tests are approximate using memory only (no storage IO). # Algorithm | Key | Encryption | Decryption xchacha20,aes-adiantum 256b 161.0 MiB/s 162.2 MiB/s
Ссылки, которыми я руководствовался:
https://gist.github.com/palopezv/792b9f0100484186c3f74cbee7b07630
https://wiki.davidl.me/index.php?title=LUKS&mobileaction=toggle_view_desktop
Я выбрал xchacha20, т.к. разница в скорости не критичная, а оно вроде как надёжнее.
xchacha20 отличается в xchacha12 количеством раундов шифрования - 20 вместо 12.
Где почитать: https://www.reddit.com/r/cryptography/comments/p3dflu/fulldiskencryption_xchacha12_vs_xchacha20/
sudo cryptsetup luksFormat --type=luks2 --sector-size=4096 -c xchacha20,aes-adiantum-plain64 -s 256 -h sha512 --use-urandom /dev/sdXX
Тут вместо sdXX написать реальный раздел как он показывается в lsblk
Дальше был забавный момент - после всего программа требует написать YES большими буковками. Просто чтобы убедиться, что человек внимательно посмотрел на команду и случайно не похерит нужный раздел.
Дальше надо подключить раздел и отформатировать
sudo cryptsetup open /dev/sdXX NAME sudo mkfs.ext4 /dev/mapper/NAME
# Open the encrypted drive sudo cryptsetup open /dev/sdXX NAME # Mount your partition mount -t ext4 /dev/mapper/NAME MOUNT_LOCATION
# Unmount your partition umount MOUNT_LOCATION # Close the decrypted drive cryptsetup close NAME
Выводы
Если хочется только домашнее хранилище - проще и дешевле подключить диск напрямую к роутеру (некоторые роутеры так умеют). Но мне хотелось иметь полноценный сервер, хоть и маленький. Своей цели я достиг и очень доволен.