Я сделал уже второй livecd, правда этот - уже совсем специального
назначения - для развертывания системы из образа. Так что у меня
выработалась некая технология изготовления таких микро-дистрибутивов.
Пакеты я беру из своего debian etch (а шо? много уже скомпилированного
софта). Но я считаю, что на liveCD система управления пакетами вроде
apt совершенно ни к чему. Поэтому перед установкой в создаваемую
систему я конвертирую deb-пакеты в обычные тарболлы (.tar.gz), благо
это совсем просто (вот скрипт).
Далее, из идеи о минимальном дистрибутиве сразу следует, что на диске
должно быть только то, что необходимо в данном случае. Поэтому
начинаем создание livecd с установки самых нужных пакетов. Например, в
последнем случае у меня был скрипт-инсталятор, написанный на
bash+dialog. Значит, на диске нужны: bash, dialog и их (жесткие)
зависимости. Скрипт использует cp с компанией - значит, нужен пакет
coreutils.
Зависимости надо как-то отслеживать, не вручную же? В качестве внешней
(по отношению к создаваемой системе) базы данных зависимостей я
использую базу apt-cache моей рабочей системы.
Все это делается, естественно, скриптами. Вот скрипт,
устанавливающий tar.gz в директорию с создаваемой системой. Этот
скрипт находит deb-пакет, конвертирует его в tar.gz (используя
вышеприведенный deb2targz), и устанавливает его. Наконец, этот
скрипт устанавливает сначала зависимости пакета, а потом и сам пакет.
Таким образом, пакеты ставятся в систему командой install-depends .
Замечание 1: скрипт install-depends устанавливает только
непосредственные зависимости пакета. Т.е. если пакет A зависит от B, а
B зависит от C, то команда install-depends A поставит пакеты A и B, но
не поставит пакет C. Это пока приходится отслеживать вручную. Благо, в
маленьких системах таких случаев мало. А для большой системы можно и
скрипт поправить (а можно и нормальный пакетный менеджер поставить).
Замечание 2: всем скриптам нужна директория, выделенная под новую
систему, и в ней директория с именем packages. Там будут складываться
установленные пакеты (deb и tar.gz), и там же будет храниться файл
installed.list со списком установленных пакетов - чтобы не
устанавливать одно и то же два раза.
Замечание 3: во всех приводимых скриптах перед использованием нужно
везде прописать правильные директории, по контексту вроде понятно,
какие. Если непонятно - спросите в комментах.
Таким образом, по кусочкам, в выделенной директории оказывается
более-менее рабочий вариант системы (по вопросам, какие пакеты надо
ставить для того, чтобы работало то или другое, например login,
загляните в LFS - полезная книжка). После того, как поставлен bash с
зависимостями, в созданную систему можно chroot'нуться (chroot
/path/to/live /bin/bash), и работать уже "изнутри".
После установки пакетов понадобится еще кое-какая доводка, чтобы
система стала совсем рабочей - написание /etc/fstab, /etc/inittab и
еще чего понадобится (в инсталяторе, например, мне ни inittab, ни
fstab не понадобились). Сюда же относится написание startup сценариев
- тут у вас полная свобода, пишите что вам надо. Для совсем простых
систем можно вообще не ставить sysvinit, а в качестве init
использовать самописный скрипт на bash.
Таким образом получается система, в которой можно работать как в
chroot-среде. Теперь нужно обеспечить ее загрузку. Для этого, прежде
всего, нужно ядро.
Можно взять дистрибутивное ядро. Оно сконфигурировано так, чтобы
загружаться на чем угодно. Есть у него недостаток - оно модульное, и
нужность тех или иных модулей определяется скриптами при каждой
загрузке. Скрипты а) выполняются относительно долго и б) сложные,
захочешь чего-то сделать по-своему - скорее всего сломаешь. Однако
пару "дистрибутивное ядро + скрипты из рабочей системы" я посоветую
для тех livecd, которые должны загружаться на совсем разных
конфигурациях железа.
Если круг оборудования более определен, можно собрать для него свое
ядро. Это окажется необходимо, если загружаться надо на самом
распоследнем железе - дистрибутивное ядро его не поддерживает. Это
также необходимо, если вы хотите применить к ядру патчи, которых нет в
дистрибутивном ядре. Например, в LiveIDE я использую SquashFS, чтоб
впихнуть всего побольше.
Для маленьких систем можно собрать монолитное ядро. Это хорошо тем,
что ядро само определит все оборудование, какое оно знает, т.е.
скрипты для определения оборудования не нужны. Конечно, эта идея не
пройдет, если поддерживать нужно очень широкий круг оборудования. Но,
например, из ядра для инсталятора я выкинул поддержку звуковых карт,
сети и еще много чего - оно в момент установки не нужно.
Для загрузки нужен загрузчик. Я использую isolinux. Настроить его
совсем несложно, все описано в документации. В качестве примера
настройки можете взять содержимое директории isolinux/ на установочном
CD вашего дистрибутива.
Загрузчик загрузит ядро. А ядро, по идее, должно запустить /sbin/init.
Но для этого оно должно примонтировать корневую ФС. А где у нас будет
корень? CD-ROM не подходит из-за того, что на разных машинах он
подключен по-разному - то он /dev/hdb, а то вовсе /dev/scd0. Поэтому
на LiveCD в качестве корневой ФС (по крайней мере, на первом этапе
загрузки) традиционно используют initrd.
initrd - это файл образа любой ФС, понимаемой ядром (поддержка этой ФС
должна быть вкомпилирована в ядро намертво, т.е. не модулем). Можно
этот образ пожать при помощи gzip, получив initrd.gz - ядро умеет
такие образы распаковывать.
Что класть в initrd? В initrd может быть, как минимум, три варианта:
* Совсем минимальная система, которая только и делает, что находит
диск и монтирует его в качестве "настоящей" корневой ФС;
* Вся система;
* Часть системы, способная загрузиться и подмонтировать остальные
части системы с cd.
Первый вариант обычно используется в "настольных" системах. Его можно
использовать и для livecd, в том случае, если основная (live) система
занимает как раз что-то около 500..700Мб. Если система занимает больше
- нужно использовать сжатие отдельных частей системы, для того, чтобы
с ними работать, нужна уже не совсем тривиальная система.
В LiveIDE у меня используется третий вариант. Директория /usr сжата в
образ squashfs, а все остальное находится в initrd.
Для маленьких систем имеет смысл использовать второй вариант -
помещение системы в initrd целиком.
Файловая система, находящаяся в памяти имеет следующие преимущества:
1) она доступна на чтение/запись (а cd и squashfs - только на чтение);
2) если, кроме ядра и initrd, с диска ничего не грузится, диск можно
достать из привода сразу после загрузки. Ну а недостаток большого
initrd очевиден - он занимает много места в памяти.
Для загрузки ядра с initrd в качестве корневой ФС нужна следующая
строка в isolinux.cfg:
В параметре ramdisk_size нужно указать число, несколько превосходящее
размер распакованного initrd.
Если в системе не используется sysvinit, а используется свой
init-скрипт, нужно дописать к этой строке что-то вроде
"init=/sbin/install-image" .
Теперь создаем образ initrd (например, так: dd if=/dev/zero of=initrd
bs=1024 count=40x1024), форматируем его (mkfs.ext2 initrd), монтируем
как loop-устройство (mount -o loop initrd /mnt/initrd), кладем туда
все, что решили положить, отмонтируем и сжимаем (gzip initrd). Создаем
директорию (mkdir output/), из которой будем собирать образ диска, в
нее кладем директорию isolinux, создаем директорию isolinux/kernel,
туда кладем ядро и initrd. По мере надобности кладем файлы/директории
в корневую директорию диска.
Для создания загрузочного iso-образа используем команду вроде
следующей
mkisofs -o /path/to/live-image.iso -r -V "My Live CD" -v -no-emul-boot
-boot-load-size 4 -boot-info-table -b isolinux/isolinux.bin -c
isolinux/isolinux.boot /path/to/output
Конечно, вряд ли все получится с первого раза. Чтобы не пороть кучу
cd-болванок, образы можно тестировать под эмулятором (qemu).
PS. Я в курсе, что это "не совсем дебиан-вей". Это, скорее, слако-вей.
Но этот способ гораздо более гибкий.
Скрипт deb2targz
#!/bin/bash
ar x $1
rm control.tar.gz
mv data.tar.gz ${1/.deb/.tar.gz}
if [ $COUNT -eq 0 ]
then echo No packages found for $1.
exit
else if [ $COUNT -gt 1 ]
then echo Found $COUNT packages for $1.
fi
fi
if [ $COUNT -gt 1 ]
then I=0
for P in ${L[*]}
do echo "[$I] $P"
I=$(($I+1))
done
fi
if [ $COUNT -eq 1 ]
then N=0
else echo -n "Which package to install? "
read N
if [ "$N" = "-1" ]
then echo Aborting.
exit 1
fi
fi
if [ "$N" = "*" ]
then LIST=${L[*]}
else LIST=${L[$N]}
fi
for PACK in $LIST
do CONT=0
TGZ=$(basename ${PACK/.deb/.tar.gz})
if grep "^$TGZ$" $LIVE/packages/installed.list -q
then if [ "$2" != "reinstall" ]
then continue
else CONT=1
fi
else CONT=1
fi
if [ $CONT -eq 1 ]
then $ECHO "Installing $TGZ... "
cp $PACK packages/
cd packages
/home/portnov/bin/deb2targz $(basename $PACK)
cd $LIVE/
if bin/installtgz packages/$TGZ $LIVE/
then if [ "$2" != "reinstall" ]
then echo $TGZ >> packages/installed.list
fi
echo Done.
else echo Failed.
fi
fi
done
Скрипт install-depends
#!/bin/bash
# (C) Portnov 2007
S=($(apt-cache depends $1 | grep Depends | awk '{print $2}'))
COUNT=${#S[*]}
echo $1 depends on $COUNT packages.
for P in ${S[*]}
do /home/portnov/RTFM/live-image/bin/find-and-install $P
done
/home/portnov/RTFM/live-image/bin/find-and-install $1 $2