Изначально я хотел сделать что-то поинтересней, но пока в наличии у меня только Raspberry Pi 2B, ограничусь модификацией стандартного образа: результат будет работать на всех версиях малинки. Свой же образ мечты соберу, когда у меня на руках будет четвёртая версия с 4 гигабайтами памяти.

Большинство инструкций (к примеру, активация ssh) на официальном сайте предполагают следующие шаги:

  1. Запись образа на MicroSD-карточку.
  2. Модификация его на рабочем устройстве/содержимого MicroSD-карточки с помощью card reader’а.
  3. Снятие образа с MicroSD-карточки.

Мне кажется такой подход и сложным, и неспортивным. По мне гораздо лучше делать так:

  1. Модифицируем образ на ПК. Лучше автоматически, к примеру, shell-скриптом, чтобы процесс был повторяемым и в него можно было легко вносить изменения.
  2. Записываем его на MicroSD-карточку.
  3. Проверяем работоспособность на устройстве.

В любом случае, я покажу как изменить стандартный образ с Raspberry Pi OS без запуска устройства и записи файлов на карточку.

Помните, что красота в глазах смотрящего. Делал я это всё ради развлечения себя же, поэтому и цели могут показаться странными. Да и моя реализация может кардинально отличаться с вашим представлением о прекрасном: благо в линуксах обычно не возброняется идти несколькими путями.

В конце будет приложена ссылка на скрипт, в котором автоматизированы все перечисленные «улучшения», в качестве же доказательства работоспособности системы после таких издевательств держите статус systemd.

systemctl status running после модификации образа

Подготовка

Предположим, что всё будет происходить в домашнем каталоге пользователя, в папке rpios. Создадим и перейдём в этот каталог:

$ mkdir ~/rpios
$ cd ~/rpios

Теперь создадим папку для чистых «входных» (input) данных, в качестве которых у нас будет выступать архив с образом и «выходных» (output) — где будет находиться модифицированный образ.

$ mkdir input
$ mkdir output

На стандартном образе 2 раздела с файловыми системами fat32 и ext4, которые монтируются как /boot и корневой раздел /, соответственно. Создадим для них точки монтирования:

$ mkdir -p mnt/boot
$ mkdir -p mnt/root

Теперь скачаем образ, всё перечисленное будет работать как на облегченной версии Raspberry Pi OS без графического окружения, так и на полной. Для унификации шагов в последствии, скачиваемый архив мы назовём одним именем.

Для того, чтобы скачать облегченную версию образа с официального сайта введите в консоле:

$ wget https://downloads.raspberrypi.org/raspios_lite_armhf_latest -O input/raspios.zip

Для полной версии:

$ wget https://downloads.raspberrypi.org/raspios_full_armhf_latest -O input/raspios.zip

Возможно, что ссылки будут не актуальны, но вы можете посетить официальный сайт и обновить их.

После загрузки скопируем оригинальный архив в каталог с результатом, разархивируем его и удалим копию:

$ cp input/raspios.zip output/raspios.zip
$ cd output
$ unzip raspios.zip
$ rm raspios.zip
$ cd ..

Теперь посмотрим содержимое папки output, там должен быть оригинальный образ Raspberry Pi OS, версия его скорее всего будет отличаться от моей, сделайте на это поправку в следующих шагах:

$ ls output/
2020-08-20-raspios-buster-armhf-lite.img

Создадим виртуальное устройство, которое бы ассоциировалось с нашим образом и найдём на нём разделы:

# losetup --find --partscan --show output/2020-08-20-raspios-buster-armhf-lite.img
/dev/loop0

Команда возвращает имя файла устройства, посмотрим, нашлись ли какие-нибудь разделы в образе (соответственно, если у вас устройство имеет другой числовой постфикс, к примеру, /dev/loop1, то нужно использовать его):

$ ls /dev | grep loop0
loop0
loop0p1
loop0p2

Всё правильно, два раздела: один для /boot, второй для корня файловой системы. Примонтируем их в наши точки монтирования:

# mount -t vfat /dev/loop0p1 mnt/boot
# mount -t ext4 /var/loop0p2 mnt/root

Подготовка закончена, все дальнейшие примеры предполагают, что мы находимся в этой точке отсчёта (чтобы не загромождать их лишними командами). Для уверенности убедимся, что мы по-прежнему в домашней директории в папке rpi:

$ cd ~/rpi

1. Изменить config.txt в образе Raspberry Pi OS

Первая идея банальна и пригодится, скорее всего, почти каждому. С помощью команда sed в скрипте или с помощью nano вручную, можно изменить файл /boot/config.txt.

К примеру, я хочу адаптировать образ к своему монитору. Убрать чёрные полоски с монитора в Raspberry и настроить разрешение в консоле:

# sed "s/#*disable_overscan=[0-9]\+/disable_overscan=1/g" -i mnt/boot/config.txt
# sed "s/#*framebuffer_width=[0-9]\+/framebuffer_width=1920/g" -i mnt/boot/config.txt
# sed "s/#*framebuffer_height=[0-9]\+/framebuffer_height=1080/g" -i mnt/boot/config.txt

2. Включить ssh по-умолчанию

Тут можно было бы создать файл с именем ssh в /boot разделе, но это слишком скучно. Лучше отключим проверку на наличие этого файла при запуске и включим запуск демона sshd в systemd операционной системы:

# rm mnt/root/etc/systemd/system/multi-user.target.wants/sshswitch.service
# ln -s /lib/systemd/system/ssh.service mnt/root/etc/systemd/system/multi-user.target.wants/ssh.service

3. Отключить swap в Raspberry Pi OS

Операционная система создаёт файл на MicroSD карточке, в который вымещается неиспользуемая оперативная память при заполнении последней больше чем на 80%. Мне кажется в принципе плохой идеей держать swap на медленной MicroSD карточке, ресурс жизни которой сильно ограничен и зависит в первую очередь от количества операций записи. На мой вкус, пусть лучше OOM-killer завершит работу приложения при заполнении памяти.

Зададим параметр ядру при загрузке, чтобы то не использовало swap в принципе. Заодно отключим службы systemd, создающие swap-файл:

# echo "vm.swappiness=0" >> mnt/root/etc/sysctl.conf
# ln -s /dev/null mnt/root/etc/systemd/system/swap.target
# rm mnt/root/etc/systemd/system/multi-user.target.wants/dphys-swapfile.service

4. Добавить файловых систем, располагающихся в оперативной памяти

Это позволит дополнительно снизить нагрузку на MicroSD карточку в Raspberry Pi OS. Особенность tmpfs состоит в том, что мы задаём крайний предел для использования оперативной памяти. Т.е. если указано 32Мб, то это вовсе не значит, что будут использоваться 32 мегабайта оперативной памяти при старте. Но предел именно такой. Для начала перенесём в оперативную память временные данные, которые становятся неактуальными между перезагрузками:

# echo "tmpfs    /tmp    tmpfs    defaults,noatime,nosuid,size=64m    0 0" >> mnt/root/etc/fstab
# echo "tmpfs    /var/tmp    tmpfs    defaults,noatime,nosuid,size=32m    0 0" >> mnt/root/etc/fstab
# echo "tmpfs    /var/run    tmpfs    defaults,noatime,nosuid,mode=0755,size=8m    0 0" >> mnt/root/etc/fstab
# echo "tmpfs    /var/spool  tmpfs    defaults,noatime,nosuid,mode=0700,gid=12,size=16m    0 0" >> mnt/root/etc/fstab

Теперь шаг более спорный: перенос всех логов в оперативную память, они перестанут сохраняться между перезагрузками, но и на карточку записи не будет:

# echo "tmpfs    /var/log    tmpfs    defaults,noatime,nosuid,mode=0755,size=8m    0 0" >> ${ROOT_MNT}/etc/fstab

5. Убрать журналирование с корневой файловой системы в образе Raspberry Pi OS

Продолжаем уменьшать количество обращений к MicroSD карточке. Этот шаг более рискованный, т.к. вредит стабильности файловой системы в случае нештатного отключение. Но, опять же на мой вкус, вполне оправдан:

  1. При внезапном отключении питания сама по себе MicroSD карточка не стабильна и вполне себе может стать неработоспособной без всяких файловых систем.
  2. Ничего важного в одном экземпляре хранить на ней нельзя, а потому потенциальная потеря данных не столь страшна.

Так как во всех примерах разделы примонтированы, то отмонтируем его:

# umount mnt/root

Затем отключим журналирование:

# tune2fs -O ^has_journal /dev/loop0p2

И монтируем раздел обратно, чтобы не вмешиваться в остаток повествования:

# mount -t ext4 /dev/loop0p2 mnt/root

6. Отключить выполнение ненужных сервисов при загрузке системы

Тут придётся загрузиться в стандартный образ и посмотреть, что в принципе systemd запускает при старте системы. Для этого стоит внимательно изучить вывод:

# systemctl list-units --state=active

Грубо говоря, это список всех задач, которые systemd выполнила после старта или выполняет прямо сейчас. Внимательно их рассматриваем и составляем чёрный список в блокнотике. Затем алгоритм простой:

  • Если файл юнита сервиса/цели есть в mnt/root/etc/systemd/ (find mnt/root/etc/ -name unit_name.service), то удаляем его.
  • Если файла нет в mnt/root/etc/systemd/, то «маскируем» его, создавая ссылку на /dev/null в mnt/root/etc/systemd/system/. Он перестанет выполняться при старте.

У меня получился такой список, у вас же, наверняка, он будет отличаться:

# ln -s /dev/null mnt/root/etc/systemd/system/time-sync.target
# ln -s /dev/null mnt/root/etc/systemd/system/resize2fs_once.service
# rm mnt/root/etc/systemd/system/bluetooth.target.wants/bluetooth.service
# rm mnt/root/etc/systemd/system/multi-user.target.wants/console-setup.service
# rm mnt/root/etc/systemd/system/multi-user.target.wants/cron.service
# rm mnt/root/etc/systemd/system/dbus-fi.w1.wpa_supplicant1.service
# rm mnt/root/etc/systemd/system/dbus-org.bluez.service
# rm mnt/root/etc/systemd/system/multi-user.target.wants/hciuart.service
# rm mnt/root/etc/systemd/system/sysinit.target.wants/keyboard-setup.service
# rm mnt/root/etc/systemd/system/multi-user.target.wants/rsync.service
# rm mnt/root/etc/systemd/system/multi-user.target.wants/wpa_supplicant.service
# rm mnt/root/etc/systemd/system/remote-fs.target.wants/nfs-client.target
# rm mnt/root/etc/systemd/system/multi-user.target.wants/nfs-client.target
# rm mnt/root/etc/systemd/system/multi-user.target.wants/remote-fs.target
# rm mnt/root/etc/systemd/system/sysinit.target.wants/systemd-timesyncd.service
# rm mnt/root/etc/systemd/system/timers.target.wants/apt-daily-upgrade.timer
# rm mnt/root/etc/systemd/system/timers.target.wants/apt-daily.timer
# rm mnt/root/etc/systemd/system/timers.target.wants/man-db.timer

7. Перевести корневую файловую систему в read-only на образе Raspberry Pi OS

Шаг, который кардинально избавит от записи на MicroSD карточку, а значит та станет реже выходить из строя. На самом деле, если не нужно подключения по ssh, то это можно сделать уже после 4 из данной статьи.

# sed /ext4/s/defaults/defaults,ro/ -i mnt/root/etc/fstab

И в целом операционная система запустится и будет работать, если же нужно подключение по ssh всё несколько усложняется:

  1. При первом запуске операционной системы генерируются ключи для сервера sshd.
  2. При подключении к пользователю в его домашнем каталоге тоже создаётся папка и файлики.

Решить первую проблему можно сгенерировав эти файлы самостоятельно и отключив соответсвующий юнит в systemd.

# ssh-keygen -q -N "" -t dsa -f mnt/root/etc/ssh/ssh_host_dsa_key
# ssh-keygen -q -N "" -t rsa -b 4096 -f mnt/root/etc/ssh/ssh_host_rsa_key
# ssh-keygen -q -N "" -t ecdsa -f mnt/root/etc/ssh/ssh_host_ecdsa_key
# rm mnt/root/etc/systemd/system/multi-user.target.wants/regenerate_ssh_host_keys.service

Со второй же я справился разместив всю домашнюю директорию пользователя в tmpfs:

# echo "tmpfs    /home/pi  tmpfs    defaults,noatime,mode=0755,uid=1000,gid=1000,size=128m    0 0" >> ${ROOT_MNT}/etc/fstab

В целом это будет полезно, мало ли захочется создать какой-то временный файл.

Завершение изменений

Осталось отмонтировать все разделы, удалить виртуальное устройство и для надежности синхронизировать состояние файловой системы в кэше и на диске:

# umount mnt/boot
# umount mnt/root
# losetup -d /dev/loop0
# sync

Заключение

Всё вышеперечисленное работает для облегченного и полного образа (с графическим окружением) Raspberry Pi OS по состоянию на октябрь 2020 года. Рецепты в целом универсальны и наверняка будут работать позже этой даты, обещанный же полный скрипт для простой автоматизации процесса располагается тут.

Кроме того, можно было бы ещё записать на образ файл настройки раскладок клавиатуры X-сервера, но я решил не повторяться. Ещё можно было описать способ установки или удаления пакетов, добавления пользователя или смены пароля для существующего. Да много чего! Надеюсь, по мере создания образа своей мечты я буду оставлять тут ссылки:

Навигация по записям