Пару дней назад уже была статья на тему: как улучшить образ Raspberry Pi OS. Там я просто редактировал образ без выполнения команд «изнутри». Но, благодаря связке chroot и QEMU, можно пойти несколько дальше: эмулировать выполнение команд arm процессора, работая на процессоре amd64 (в принципе, архитектура хоста не столь важна, суть в том что она отличается), внутри образа операционной системы без запуска этой самой системы и целевого устройства.
Таким образом можно будет сделать много чего интересного, к примеру, скомпилировать пакет под Raspberry Pi OS. Я же установлю эмулятор терминала sakura, просто для демонстрации работоспособности.

Подготовка
qemu-arm-static
Для начала нам нужен статически (чтобы не зависеть от операционной системы хоста) слинкованный исполняемый файл эмулятора QEMU под архитектуру arm. Обычно этот файл называется qemu-arm-static. В Debian/Ubuntu есть пакет с ним и его можно легко установить:
# apt install qemu-user-static
Вполне вероятно, что и в вашем дистрибутиве есть что-то подобное. А вот в ArchLinux же исполняемые файлы QEMU слинкованы динамически и нужно ручками всё собирать из AUR.
Такой способ не универсальный. Для дистрибутивов, в которых нет статически слинкованных исполняемых файлов QEMU, можно поискать этот файл в сети. Так как он слинкован статически, то зависит разве что от версии ядра и можно вскрыть тот же пакет для Ubuntu под архитектуру хоста и получить бинарный файл. Я же нашёл такую ссылку для amd64, где можно скачать qemu-arm-static.
Затем можно поместить скаченный файл в /usr/bin:
# cp ~/Downloads/qemu-arm-static /usr/bin
# chmod +x /usr/bin/qemu-arm-static
Найдём по какому пути располагается qemu-arm-static в системе:
# find / -name qemu-arm-static 2>/dev/null
/usr/bin/qemu-arm-static
В дальнейшем я буду использовать этот путь.
binfmt_misc
Поддержка binfmt_misc должна быть включена в ядре. Оставлю это на ваш откуп, так как в ядре моего дистрибутива она уже была включена по-умолчанию:
# cat /proc/sys/fs/binfmt_misc/status
enabled
Добавим путь к нашему qemu-arm-static для обработки бинарных файлов под архитектуру arm:
# echo ':qemu-arm:M::\x7fELF\x01\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x28\x00:\xff\xff\xff\xff\xff\xff\xff\x00\xff\xff\xff\xff\xff\xff\xff\xff\xfe\xff\xff\xff:/usr/bin/qemu-arm-static:CF' > /proc/sys/fs/binfmt_misc/register
Если всё хорошо, то вывод выполнения следующих команд должен быть похож:
# ls /proc/sys/fs/binfmt_misc/
DOSWin qemu-arm register status
# cat /proc/sys/fs/binfmt_misc/qemu-arm
enabled
interpreter /usr/bin/qemu-arm-static
flags: OCF
offset 0
magic 7f454c4601010100000000000000000002002800
mask ffffffffffffff00fffffffffffffffffeffffff
Если же где-то закралась ошибка, то всегда можно удалить ассоциацию (но на данном этапе делать этого не нужно):
# echo -1 > /proc/sys/fs/binfmt_misc/qemu-arm
# ls /proc/sys/fs/binfmt_misc/
DOSWin register status
Образ Raspberry Pi OS
Образ нужно скачать с официального сайта и разархивировать. Предположим, что в нашей рабочей директории уже есть образ операционной системы (для статьи нет разницы, full или lite версия ОС, поэтому он назван нейтрально):
# ls
raspios.img
В дальнейшем будет использовано это имя образа.
chroot в образ Raspberry Pi OS
Для начала создадим виртуальное устройство, которое было бы отображением образа операционной системы и найдём на нём разделы:
# losetup --show --find --partscan raspios.img
/dev/loop0
/dev/loop0 — это имя виртуального устройства, которое назначила ему операционная система. На нём должно быть два раздела:
$ ls /dev/loop0*
/dev/loop0 /dev/loop0p1 /dev/loop0p2
- /dev/loop0p1 — раздел с точкой монтирования /boot.
- /dev/loop0p2 — корень файловой системы, точка монтирования /.
Создадим точку монтирования для разделов и подключим их:
$ mkdir root
# mount -t ext4 /dev/loop0p2 root
# mount -t vfat /dev/loop0p1 root/boot
Теперь осталось примонтировать служебные папки файловой системы, чтобы операционная система не потерялась после chroot и была вполне работоспособна:
# mount --bind /dev root/dev/
# mount --bind /dev/pts root/dev/pts
# mount --bind /sys root/sys/
# mount --bind /proc root/proc/
Теперь можно сделать chroot в Raspberry Pi OS:
# chroot root
Всё, мы внутри, и можно изменять образ по-своему желанию. При этом, благодаря эмуляции, мы можем выполнять большинство команд. Конечно, есть ограничения, ведь сама по себе операционная система, а значит и systemd, и демоны не были запущены — что-то да не будет работать. Но у нас есть работоспособная сеть с операционной системы хоста, вкупе с эмуляцией этого хватит для удаления программ, их установки и компиляции.
Но почему-то глобальная переменная PATH выглядит странной и некоторые утилиты недоступны. Это легко исправить изменив её:
# export PATH=/usr/local/bin:/usr/local/sbin:/usr/bin:/usr/sbin:/bin:/sbin

Решил для примера установить эмулятор терминала:
# apt update
# apt install sakura
# apt clean
# uname -a
Всё вполне работает, что и видно на первом изображении в посте.
Завершение изменений
Для выхода из chroot следует воспользоваться командой:
# exit
Можно удалить файл с историей команд bash’a, который был записан в сеансе:
# rm root/root/.bash_history
И какие-то другие временные файлы, созданные вами. Затем останется только отмонтировать все файловые системы в обратном порядке, удалить виртуальное устройство и сбросить дисковый кэш.
# umount root/proc/
# umount root/sys/
# umount root/dev/pts
# umount root/dev/
# umount root/boot/
# umount root/
# losetup -d /dev/loop0
# sync
Изменённый образ готов для записи на MicroSD карточку!
Заключение
Осталось за кадром пару нюансов:
- На стандартном образе сравнительно небольшой объём свободного пространства. Ведь по задумке тот расширяется при первом запуске Raspberry Pi OS. Его можно увеличить.
- Всё это можно и нужно автоматизировать.
Позже планирую их раскрыть.