BusyBox i tworzenie własnego systemu operacyjnego

BusyBox i tworzenie własnego systemu operacyjnego

Tworzenie własnego systemu operacyjnego
Tworzenie własnego systemu operacyjnego
Źródło zdjęć: © Licencjodawca
20.03.2024 17:57

Uważa się, że zbudowanie systemu operacyjnego jest wymagającym zadaniem. To oczywiście prawda, szczególnie w sytuacji, gdy taki system piszemy od zupełnych podstaw. Mniej skomplikowanym zagadnieniem jest użycie gotowych (w znaczeniu dostępności kodu źródłowego) komponentów, natomiast ich integracja wciąż stanowi dość zaawansowaną procedurę.

Trzeba jednak przyznać, że zasady działania systemów operacyjnych to wyjątkowo interesująca tematyka. Polecam zapoznać się z teoretycznym opracowaniem z tego zakresu, dostępne są obszerne branżowe pozycje książkowe.

W tym artykule opiszę kroki prowadzące do uzyskania działającego systemu, który dodatkowo będzie posiadał możliwość hostowania podstawowych aplikacji napisanych w języku PHP. Do systemu będzie możliwy dostęp SSH oraz przez zwykłą konsolę. Zaczniemy od przedstawienia BusyBox.

Dalsza część artykułu pod materiałem wideo

BusyBox

BusyBox to pojedynczy skompilowany plik binarny o rozmiarze zbliżonym do 1 MB zawierający funkcje wielu typowych narzędzi dostępnych w systemach UNIX (nie są to oryginały, a customowe wersje przygotowane przez twórców BusyBox). Dzięki tej prostocie i pewnej uniwersalności znajduje zastosowanie w systemach wbudowanych (embedded), gdzie kluczowa jest oszczędność zasobów. Używany jest także w systemie Alpine. Bardziej rozbudowany, choć sprowadzający się do podanej tutaj definicji opis BusyBox znajdziemy w tym miejscu.

Opisywane narzędzie powinno być dostępne w zdecydowanej większości systemów Linux, jedynie wersja może nie być najbardziej aktualna. Przykładowo w systemie Ubuntu 22.04 i jego domyślnym repozytorium znajdziemy wersję 1.30, gdzie najnowszą wersją nie wymagającą kompilacji we własnym zakresie jest 1.35. Gotowe do użycia pliki BusyBox dostępne są pod adresem https://busybox.net/downloads/binaries/. Binarkę zapisujemy w katalogu /usr/bin i nadajemy jej prawo do wykonania.

Uruchomienie polecenia busybox zwróci listę dostępnych funkcji.

Lista funkcji BusyBox.
Lista funkcji BusyBox.© AVLab

Podanie nazwy dowolnego dostępnego polecenia jako parametr umożliwi jego praktyczne wykorzystanie, co widać na poniższym zrzucie ekranu. Część wbudowanych narzędzi wymaga uprawnień root, więc próby ich uruchomienia na prawach zwykłego użytkownika nie przyniosą efektów.


Przykładowe polecenia z użyciem BusyBox.
Przykładowe polecenia z użyciem BusyBox.© AVLab

Sam BusyBox (niezależny od pliku z /usr/bin) został zawarty również w plikach initrd, które w środowiskach Linux odpowiadają za wykonanie kroków przedstartowych. Jest ładowany w przypadku niepowodzenia rozruchu systemu operacyjnego.

Wykonania polecenia ls w initramfs.
Wykonania polecenia ls w initramfs.© AVLab

Plik initrd.img z katalogu /boot to najczęściej link symboliczny do określonej wersji initial ramdisk powiązanej z aktualnym kernelem. Warto spróbować przeprowadzić modyfikację tego pliku. Do wypakowania służy polecenie unmkinitramfs, w którym oprócz nazwy pliku initrd należy podać nazwę katalogu wynikowego.

Modyfikacja initrd.img.
Modyfikacja initrd.img.© AVLab

Od razu można zauważyć, że właściwa struktura katalogów znajduje się w main. Zapisałem w tym katalogu plik tekstowy, który następnie odczytamy z poziomu initramfs. Sposób ponownego zbudowania initrd z tych trzech katalogów niestety nie jest zbyt oczywisty. Należy wykonać poniższe polecenia.

cd early

find . -print0 | cpio --null --create --format=newc > ../../initrd.img-5.15.0-78-generic

cd ../early2

find kernel -print0 | cpio --null --create --format=newc >> ../../initrd.img-5.15.0-78-generic

cd ../main

find . | cpio --create --format=newc | xz --format=lzma >> ../../initrd.img-5.15.0-78-generic

Typ oryginalnego pliku jak i utworzonego przez nas jest taki sam, więc można się spodziewać, że initrd.img zostanie prawidłowo wykonany. Nazwa nowego pliku jest zgodna z pierwotną, dzięki czemu nie musimy wprowadzać żadnych dodatkowych zmian w grub.cfg.


Porównanie plików initrd.
Porównanie plików initrd. © AVLab

Podmieniamy plik w katalogu /boot. Przy kolejnym niepowodzeniu rozruchu zobaczymy utworzony przez nas plik tekstowy.

Dodany własny plik w initramfs.
Dodany własny plik w initramfs.© AVLab

BusyBox jako kontener Docker

BusyBox można uruchomić w formie kontenera (oficjalny obraz ma ponad miliard pobrań) i sprawdza się to w niektórych procesach CI/CD W AVLab.pl wykorzystywany jest autorski system do gromadzenia i analizowania złośliwych adresów URL. W jednym ze stage’y CI/CD dla tego projektu przez pewien czas używałem obrazu BusyBox do utworzenia archiwum TGZ z kodem aplikacji, które następnie z użyciem bardziej złożonego obrazu było odpowiednio wysyłane na docelowy serwer. Po dodaniu funkcjonalności skanowania i związanej z tym konieczności zastosowania innego obrazu dalsze utrzymywanie pojedynczego stage’a z BusyBox przestało być zasadne. Natomiast zawsze powinniśmy używać jak najmniejszych obrazów do zadań, które nie wymagają specjalnych zasobów.

Na Stack Overflow znalazłem całkiem zabawne pytanie, w którym autor wyraził przekonanie, że rzekomo rozumiał zastosowanie Docker’a do czasu, aż nie natknął się na obraz BusyBox.

Przykładowy start kontenera można zainicjować w ten sposób:

docker run -it --rm busybox

Nie dodaliśmy parametru -d, przez co kontener nie uruchomił się w tle i uzyskaliśmy bezpośrednio dostęp do jego powłoki SH.

BusyBox uruchomiony w kontenerze Docker.
BusyBox uruchomiony w kontenerze Docker.© AVLab

BusyBox zawiera serwer WWW httpd. Nazwa kojarzy się z serwerem Apache, ale tutaj zastosowano własne rozwiązanie, którego kod źródłowy (jeden plik C) i jednocześnie dokumentacja znajduje się w repozytorium projektu. Możemy uruchomić nowy kontener z serwerem httpd działającym w trybie foreground (właściwym dla kontenerów).

docker run -d -it -p 8090:80 --name httpd busybox /bin/httpd -f -h /var/www -p 80

Dodaliśmy przekierowanie z lokalnego portu 8090 na port 80 w kontenerze. Po wejściu na ten port zobaczymy stronę z kodem błędu 404, ponieważ w katalogu /var/www nie ma żadnego pliku index.

Błąd 404 serwera httpd.
Błąd 404 serwera httpd.© AVLab

Jedynym edytorem tekstu zawartym w BusyBox jest vi, dlatego nie pozostaje nam nic innego, jak utworzyć plik /var/www/index.html z dowolną zawartością.

Przeładowanie strony spowoduje teraz wyświetlenie tego pliku.

Wyświetlenie zawartości pliku index przez serwer httpd.
Wyświetlenie zawartości pliku index przez serwer httpd.© AVLab

Serwer httpd w odpowiedzi wysyła standardowe nagłówki HTTP.

mgiza@web:~$ curl -I 10.0.0.30:8090

HTTP/1.1 200 OK

Date: Sun, 21 Jan 2024 19:58:49 GMT

Connection: close

Content-type: text/html

Accept-Ranges: bytes

Last-Modified: Sun, 21 Jan 2024 19:57:02 GMT

ETag: "65ad770e-a3"

Content-Length: 163

httpd posiada możliwość wykonania skryptów PHP (jako CGI). Obraz BusyBox jest przystosowany do uruchamiania polecenia php (zawiera pewne biblioteki systemowe), natomiast i tak konieczna jest kompilacja. Sugeruję zbudowanie własnego obrazu zawierającego pliki i katalogi utworzone po kompilacji. Oprócz tego niezbędne jest dodanie konfiguracji httpd, stąd użycie docker-compose wydaje się najlepszym rozwiązaniem.

Tworzymy plik httpd.conf z poniższą zawartością:

H:/home/www/strona

I:index.html

*.php:/bin/php-cgi

W katalogu strona umieszczamy przykładową witrynę. W moim przypadku do pliku index.html dodałem odnośnik do pliku info.php zawierającego kod:

<?php phpinfo();

Dzięki temu będziemy mieć potwierdzenie, że PHP jest poprawnie obsługiwany przez httpd. Do kompilacji potrzebne będą pewne pakiety:

sudo apt install make gcc pkg-config libxml2-dev libsqlite3-dev

make i gcc od zawsze są związane z procesami kompilacji, pozostałe trzy są wymagane do kompilacji PHP na Ubuntu. W przypadku innych dystrybucji istnieje szansa, że brakuje nam większej ilości pakietów. Wtedy skrypt configure zwróci błąd informujący o potrzebie doinstalowania danej biblioteki (ich nazwy niestety nie zawsze wskazują jednoznacznie pakiety dostarczające daną bibliotekę).

Kod źródłowy PHP znajdziemy na stronie https://www.php.net/downloads, obecnie najnowsze wydanie to 8.3.2. Musimy wprowadzić niewielką zmianę w kodzie, która zapobiegnie otrzymywaniu błędu Security Alert! The PHP CGI cannot be accessed directly przy próbie wykonania skryptów PHP z poziomu przeglądarki. Interesuje nas plik sapi/cgi/cgi_main.c i linie:

STD_PHP_INI_BOOLEAN("cgi.force_redirect", "1", PHP_INI_SYSTEM, OnUpdateBool //(...)

php_cgi_globals->force_redirect = 1;

w których wartości 1 zamieniamy na 0.

Kod jest przygotowany, więc następnie wykonujemy skrypt configure z tymi parametrami:

./configure --enable-static --prefix=/home/mgiza/httpd/php --disable-all

–enable-static wymusi zbudowanie bibliotek statycznych, co jest istotne w minimalnych środowiskach. Ta opcja powinna być aktywna domyślnie. –prefix określa katalog, do którego zostaną skopiowane dane wynikowe po wykonaniu polecenia make install (podana ścieżka musi być bezwzględna). Z kolei –disable-all sprawi, że kompilacja obejmie wyłącznie rozszerzenia niezbędne do działania PHP (co zmniejsza rozmiar danych wynikowych oraz znacznie skraca czas kompilacji).

Wykonanie poleceń make i make install spowoduje skompilowanie kodu oraz skopiowanie danych wynikowych do podanego katalogu.

Wykonanie make install dla kodu źródłowego PHP.
Wykonanie make install dla kodu źródłowego PHP.© AVLab

Tworzymy pliki Dockerfile i docker-compose.yml.

FROM busybox:latest

COPY php / 

WORKDIR /home/www/strona

ENTRYPOINT ["/bin/httpd", "-f"]

version: '2'

services:

busybox_httpd:

image: busybox_httpd

container_name: busybox_httpd

hostname: busybox_httpd

volumes:

- ./httpd.conf:/etc/httpd.conf

- ./strona:/home/www/strona

restart: unless-stopped

ports:

- 8090:80

Możemy porównać rozmiar zbudowanego obrazu z "czystym" obrazem BusyBox.

Sprawdzenie rozmiarów obrazów Docker.
Sprawdzenie rozmiarów obrazów Docker.© AVLab

Ostatecznie po uruchomieniu kontenera kod PHP jest interpretowany, co potwierdza poniższy zrzut ekranu.

Wykonanie phpinfo() w kontenerze Docker.
Wykonanie phpinfo() w kontenerze Docker.© AVLab

Poprawnie działa ponadto polecenie php wykonane w kontenerze.

Wykonanie polecenia php w kontenerze Docker.
Wykonanie polecenia php w kontenerze Docker.© AVLab

Kompilowanie składników systemu

Zajmiemy się teraz właściwym procesem tworzenia systemu operacyjnego. Najpierw utworzymy zwykły bootowalny plik ISO (Live CD), który będzie wykorzystywany do testów działania systemu. Systemy Live CD mogą być również użyteczne celem przeprowadzenia podstawowej diagnostyki i ewentualnej naprawy (czy nawet skopiowania danych), gdy nasz główny system operacyjny z jakiegoś powodu ulega awarii przy próbie uruchomienia.

Zanim jednak przystąpimy do tego zadania, to w pierwszej kolejności wymagana jest kompilacja kluczowych części tworzonego systemu. Oprócz PHP potrzebujemy serwera SSH, pakietu do obsługi LVM (niezbędny w przypadku operacji na partycjach LVM) i samego BusyBox (wraz z pewną ilością dodatkowych plików) jako narzędzia dostarczającego polecenia systemowe. Nie można też zapomnieć o kompilacji kernela, który jest najważniejszym elementem każdego systemu, wraz z modułami (dla naszego zastosowania jedynie podstawowe — ext4 i ntfs). Dodatkowo pojawi się konieczność napisania własnego pliku init, służącego do początkowej inicjalizacji systemu.

Nasz system operacyjny nie będzie przeznaczony do standardowej instalacji. Absolutnie nie można określić tego ograniczeniem. Przy użyciu prostej składni polecenia grep ustalimy na podstawie zawartości pliku /etc/shadow, czy użytkownik root ma ustawione hasło, a jeśli nie, to zostanie ustawione hasło domyślne, np. root1234. Tę logikę zaimplementujemy właśnie w /init.

BusyBox zapewnia gotowy plik /sbin/init, natomiast większą wartość ma jednak przygotowanie autorskiego rozwiązania. Skorzystamy oczywiście z /sbin/init, ponieważ do wykonania pewnych operacji (załadowanie procesów działających w tle, przeprowadzenie konfiguracji sieci itd.) zwyczajnie sprawdza się lepiej.

Dropbear

Dropbear to serwer SSH, który w zastosowaniach embedded jest właściwie bezkonkurencyjny. Nie wymaga żadnych dodatkowych bibliotek, a wielkość pliku wykonywalnego potrafi być zaskakująco niewielka. Przed kompilacją należy doinstalować pakiety autoconf i zlib1g-dev.

Archiwum z najnowszą wersją widoczne jest zawsze pod adresem https://matt.ucc.asn.au/dropbear/. Wykonujemy polecenia:

autoconf

./configure --enable-static --prefix="/home/www/images/dropbear"

Pojawi się informacja configure: Now edit localoptions.h to choose features. Kopiujemy plik default_options.h jako localoptions.h, nie ma potrzeby edycji domyślnych ustawień. Jak zawsze wykonujemy następnie polecenia make i make install, natomiast przy make ważne jest określenie plików binarnych, które zamierzamy otrzymać:

make PROGRAMS="dropbear dbclient dropbearkey dropbearconvert scp"

Można zauważyć, że kompilacja się powiodła, a dropbear ma możliwość uruchomienia.

Działanie binarki dropbear.
Działanie binarki dropbear.© AVLab

LVM

Wspólne archiwum zawierające źródła wszelkich pakietów odpowiadających za obsługę LVM w systemie znajdziemy na stronie https://sourceware.org/ftp/lvm2/. Przed kompilacją musimy zainstalować pakiet libaio-dev, a sama procedura nie wymaga żadnych nietypowych czynności. Standardowo wykonujemy skrypt configure podając –prefix, a dalej make i make install.

BusyBox

Kody źródłowe zostały umieszczone w tym miejscu: https://busybox.net/downloads/. Aktualnie najnowsza wersja znajduje się w archiwum busybox-1.36.0.tar.bz2. Potrzebny będzie pakiet libncurses-dev. Po wypakowaniu katalogu wykonujemy w nim w zasadzie analogiczne polecenia, jednak z dwoma dodatkowymi na samym początku:

make defconfig

make menuconfig

Zostanie wyświetlony "interaktywny" prompt, z którego poziomu przechodzimy do Settings i klawiszem spacji zaznaczamy checkbox znajdujący się przy opcji Build static binary (no shared libs) w sekcji Build Options.

make menuconfig dla BusyBox.
make menuconfig dla BusyBox.© AVLab

Dalej wybieramy dwukrotnie Exit z dolnego paska i przy pytaniu Do you wish to save your new configuration? wybieramy, co oczywiste, Yes. Możemy teraz wykonać make i make install. W katalogu _install zostaną utworzone symlinki do pliku /bin/busybox odpowiadające nazwom dostępnych poleceń. To oczekiwany rezultat. W tym momencie posiadamy wszystkie kluczowe z punktu widzenia użyteczności elementy.

Wstępne przygotowanie

Do katalogu _install kopiujemy całą zawartość skompilowanego katalogu z PHP, katalog z witryną (home/www/strona) oraz konfigurację serwera WWW, czyli plik httpd.conf. Wszystkie konfiguracje najlepiej przechowywać w katalogu etc, to tradycyjna praktyka w systemach Linux.

Dalej w pliku etc/hostname zapisujemy dowolnie wybraną nazwę hosta (w moim przypadku linux). Z kolei w pliku etc/issue możemy umieścić komunikat "powitalny", który będzie wyświetlany przed każdym logowaniem w konsoli. Zwykły tekst nie wygląda może świetnie, dlatego proponuję wygenerować tzw. ASCII art z dowolną frazą. Trzeba mieć świadomość, że w małej konsoli nie wszystkie napisy zostaną wyświetlone w całości. Widoczny poniżej ASCII art stanowi pewnego rodzaju kompromis pomiędzy estetyką a ograniczeniami konsoli (jedynie znak "x" jest częściowo przysłonięty).

ASCII art
ASCII art© AVLab

Kopiujemy skompilowaną zawartość z katalogów wynikowych dropbear i lvm (tutaj można pominąć podkatalog usr/share). Tworzymy pusty katalog root, który będzie katalogiem domowym dla wiadomego użytkownika oraz serię plików w katalogu etc.

group

root:x:0:

crontab:x:112:

passwd

root:x:0:0:root:/root:/bin/sh

shadow (skopiowałem odpowiednią linię z pliku /etc/shadow systemu Ubuntu)

root:*:19579:0:99999:7:::

resolv.conf

nameserver 10.0.0.1

Dla pliku etc/shadow bardzo ważne pod względem bezpieczeństwa jest ustawienie chmod 640, aby nikt oprócz użytkownika root nie miał do niego dostępu.

BusyBox zapewnia obsługę sieci. Zamiast ręcznej konfiguracji za pomocą serii poleceń przy każdym uruchomieniu bardziej profesjonalnym rozwiązaniem będzie zapis ustawień w pliku etc/network/interfaces:

auto lo

iface lo inet loopback

auto eth0

iface eth0 inet static

address 10.0.0.100

netmask 255.255.255.0

gateway 10.0.0.1

dns-nameservers 10.0.0.1

Aby interfejsy sieciowe mogły zostać włączone / wyłączone, musimy jeszcze utworzyć kilka katalogów w etc/network:

mkdir etc/network/{if-pre-up.d,if-up.d,if-post-down.d,if-down.d}

Ważnym aspektem jest także poprawna obsługa daty i czasu (strefy czasowe). W tym celu w pliku etc/timezone należy podać właściwą dla nas strefę (Europe/Warsaw), skopiować katalog /usr/share/zoneinfo z naszego systemu (do usr/share) oraz utworzyć plik etc/profile, w którym zapiszemy

export TZ=CET

Do obsługi cron’a niezbędny jest katalog var/spool/cron/crontabs wraz z odpowiednimi uprawnieniami:

chmod 730 var/spool/cron/crontabs

chmod +t var/spool/cron/crontabs

Tworzymy jeszcze pusty plik var/log/wtmp, z którego korzysta polecenie last wyświetlające bieżące i ostatnie logowania użytkowników w systemie.

Do naszego systemu dodaliśmy obsługę PHP oraz LVM, natomiast brakuje nam kilku bibliotek. Co ciekawe, jeśli zbudujemy i uruchomimy system z pominięciem tych bibliotek, to przy próbie wykonania np. php -v, czy nawet /bin/php -v zostanie zwrócony komunikat informujący o tym, że nie znaleziono polecenia php (pomimo że binarka znajduje się w katalogu /bin i jest wykonywalna). Taka "reakcja" świadczy właśnie o braku niezbędnych bibliotek. Dodam też, że serwer httpd w tej sytuacji zwróci kod błędu 404 dla wszystkich skryptów PHP wykonywanych z poziomu przeglądarki.

Polecenie ldd wypisze listę bibliotek wymaganych przez oba pliki binarne. Widoczne biblioteki wystarczy skopiować z głównego systemu do analogicznych lokalizacji w budowanym systemie. Tę samą procedurę należało zastosować podczas tworzenia chroot, o czym pisałem w dedykowanym artykule.

Działanie polecenia ldd.
Działanie polecenia ldd.© AVLab

Kompilacja kernela

Przeprowadzenie kompilacji jądra Linux nie jest skomplikowane, jednak z pewnością zajmie więcej czasu niż wszystkie pozostałe. Instalujemy wymagane pakiety:

apt install flex bison libelf-dev libssl-dev

Link do archiwum z kodem najnowszej wersji kernela znajdziemy bezpośrednio na stronie https://kernel.org/ (Latest Release). Po wypakowaniu wykonujemy polecenie make defconfig. Zostanie utworzony plik .config, w którym opcjom CONFIG_EXT4_FS i CONFIG_NTFS_FS przypisujemy m. Dzięki temu będzie możliwość skompilowania modułów kernela do obsługi tych dwóch systemów plików.

Wykonujemy dalej make (można z parametrem -j podając ilość dostępnych CPU), po czym zobaczymy dwa "pytania" dotyczące systemu NTFS. Debugging nie będzie konieczny, natomiast wsparcie zapisu danych na dysku z tym systemem plików jak najbardziej.

Kompilacja modułów kernela.
Kompilacja modułów kernela.© AVLab

Po skończeniu kompilacji plik bzImage z kernelem zostanie zapisany w katalogu arch/x86/boot. Zarówno plik kernela (najlepiej skopiować go pod nazwą vmlinuz) oraz pliki System.map i .config kopiujemy w inne miejsce, ponieważ będą potrzebne przy tworzeniu partycji rozruchowej. Teraz jest czas na skompilowanie wspomnianych wcześniej modułów. Służą do tego polecenia

make modules ARCH=x86

make modules_install ARCH=x86 INSTALL_MOD_PATH=/home/www/images

Moduły zapisały się w katalogu lib w widocznej ścieżce. Zawarte zostało niepotrzebne dowiązanie symboliczne build (lib/modules/6.7.1/build) — można je usunąć.

Tworzenie skryptu init i obrazu systemu

Kopiujemy zawartość katalogów _install i lib z modułami do osobnej lokalizacji. System jest praktycznie gotowy, pozostało nam wyłącznie przygotowanie skryptu init. Na początku skrypt powinien utworzyć kilka niezbędnych do pracy systemu katalogów.

#!/bin/sh

[ -d /dev ] || mkdir /dev

[ -d /root ] || mkdir -m 0700 /root

[ -d /sys ] || mkdir /sys

[ -d /proc ] || mkdir /proc

[ -d /tmp ] || mkdir /tmp

[ -d /run ] || mkdir /run

mkdir -p /var/lock

Skrypt sprawdza więc, czy wypisane katalogi istnieją. Jeśli warunek nie jest prawdziwy, to zostaną utworzone. Do katalogów systemowych należy zamontować specjalne systemy plików.

mount -t sysfs -o nodev,noexec,nosuid sysfs /sys

mount -t proc -o nodev,noexec,nosuid proc /proc

mount -t devtmpfs -o nosuid,mode=0755 udev /dev

mkdir /dev/pts

mount -t devpts devpts /dev/pts

mount -t tmpfs -o "nodev,noexec,nosuid,mode=0755" tmpfs /run

BusyBox nie ładuje automatycznie modułów kernela, więc polecenie użycie polecenia mount w celu zamontowania partycji ext4 czy NTFS musiałby zawierać parametr -t lub być poprzedzone poleceniem modprobe . Byłoby to uciążliwe, dlatego dodamy proste polecenie, w którym dla każdej linii z pliku /etc/modules.conf wykonujemy modprobe. Nie ma możliwości załadowania kilku modułów jednocześnie (polecenie modprobe nic wtedy nie zwraca, więc wydaje się, że zadziałało, ale nie jest to prawdą).

while IFS= read -r modul; do modprobe $modul; done < /etc/modules.conf

Ustawiamy także hostname dla naszego systemu.

if [ -f "/etc/hostname" ]; then

hostname -F /etc/hostname >/dev/null 2>&1

fi

Podnosimy interfejsy sieciowe.

ifup lo -f

ifup eth0 -f

Wykonujemy sprawdzenie, czy istnieje katalog i pliki potrzebne do działania Dropbear.

[ -d /etc/dropbear ] || mkdir /etc/dropbear

[ -f /etc/dropbear/dropbear_rsa_host_key ] || dropbearkey -t rsa -f /etc/dropbear/dropbear_rsa_host_key

[ -f /etc/dropbear/dropbear_dss_host_key ] || dropbearkey -t rsa -f /etc/dropbear/dropbear_dss_host_key

[ -f /etc/dropbear/dropbear_ecdsa_host_key ] || dropbearkey -t rsa -f /etc/dropbear/dropbear_ecdsa_host_key

[ -f /etc/dropbear/dropbear_ed25519_host_key ] || dropbearkey -t rsa -f /etc/dropbear/dropbear_ed25519_host_key

Ustawiamy domyślne hasło root, gdy nie zostało zmienione, aby umożliwić zalogowanie do systemu.

is_haslo=$(grep '^root:' /etc/shadow|cut -d: -f2)

if [ "$is_haslo" = "!" ] || [ "$is_haslo" = "*" ]; then

echo root:root1234 | chpasswd

fi

unset is_haslo

Na koniec przekazujemy działanie do skryptu /sbin/init. exec jest tutaj bardzo istotny, ponieważ /sbin/init musi mieć ustawiony PID 1 (pierwszy proces w systemie).

exec /sbin/init

Nadajemy prawo do wykonania dla utworzonego skryptu init. W dalszej kolejności tworzymy katalog etc/init.d i plik etc/init.d/rcS o następującej zawartości:

crond

dropbear

httpd

Jest to lista poleceń, które mają zostać uruchomione w tle (deamon) po każdym starcie systemu. Dodajemy też plik etc/inittab:

::sysinit:/etc/init.d/rcS

::ctrlaltdel:/sbin/reboot

::shutdown:/sbin/swapoff -a

::shutdown:/bin/umount -a -r

::respawn:/sbin/getty 38400 tty0

Za interpretację tego pliku odpowiada właśnie skrypt /sbin/init. Oba dodane pliki muszą mieć ustawione prawo do wykonania. Pozostało nam już właściwie tylko utworzenie pliku etc/modules.conf, którego zawartości można się domyślić:

ext4

ntfs

Katalog główny naszego systemu ostatecznie wygląda w ten sposób:

Bazowy obraz systemu.
Bazowy obraz systemu.© AVLab

Całą zawartość widocznego katalogu zapisujemy do odpowiedniego pliku initrd.img poleceniem

find . -print0 | cpio --null --create --format=newc > ../initrd.img

Tworzenie bootowalnego pliku ISO

W celu utworzenia pliku ISO posiadającego możliwość uruchomienia systemu (z pliku initrd.img) skorzystamy z narzędzia ISOLINUX (do pobrania stąd). Tworzymy katalog cdboot/isolinux. Do katalogu cdboot kopiujemy pliki vmlinuz i initrd.img. Natomiast do katalogo cdboot/isolinux przenosimy z rozpakowanego archiwum pliki bios/core/isolinux.bin i bios/com32/elflink/ldlinux/ldlinux.c32. W tym miejscu musi znaleźć się także plik konfiguracyjny isolinux.cfg:

DEFAULT linux

LABEL linux

KERNEL /vmlinuz

APPEND initrd=/initrd.img

Docelowy plik ISO utworzymy narzędziem mkisofs (z pakietu genisoimage).

mkisofs -o linux.iso -b isolinux/isolinux.bin -c isolinux/boot.cat -no-emul-boot -boot-load-size 4 -boot-info-table cdboot

Możemy go nawet zamontować do wybranego katalogu. Jak widać, plik jest poprawny i zawiera potrzebne dane.

Zamontowanie pliku ISO.
Zamontowanie pliku ISO. © AVLab

Testy systemu Live CD

Uruchomiłem maszynę wirtualną z systemem z utworzonego pliku ISO. System został poprawnie załadowany i jest gotowy do pracy.

Logowanie do systemu.
Logowanie do systemu.© AVLab
Wykonanie poleceń w systemie Live CD.
Wykonanie poleceń w systemie Live CD.© AVLab

Możliwe jest połączenie poprzez SSH. Widać, że usługi nasłuchują na właściwych portach, a polecenie php jest wykonywane.

Połączenie SSH.
Połączenie SSH.© AVLab

Serwer httpd obsługuje skrypty PHP.

Obsługa PHP przez serwer httpd.
Obsługa PHP przez serwer httpd.© AVLab

Nie ma trudności z zamontowaniem partycji LVM (z systemem plików ext4) po uruchomieniu ISO na innej maszynie.

Zamontowanie partycji LVM.
Zamontowanie partycji LVM.© AVLab

Podobnie z systemem NTFS na maszynie z Windows Server.

Zamontowanie partycji NTFS.
Zamontowanie partycji NTFS.© AVLab

Tworzenie nośnika z systemem

Przygotowany przez nas system Live CD funkcjonuje w zamierzony sposób. Posiada jednak pewną wadę — wszystkie wprowadzone w nim zmiany (dodanie użytkownika, utworzenie plików, itd.) bytują wyłącznie w pamięci operacyjnej. Dopóki nie zapiszemy ich na nośniku, to po prostu zostaną zachowane do czasu wyłączenia (polecenie poweroff) czy restartu.

Rozwiązaniem jest utworzenie na dysku partycji z systemem oraz dodanie standardowego w środowiskach Linux boot manager’a GRUB. Sugeruję właściwie utworzyć dwie partycje dla katalogu głównego / i osobnego dla /boot, w którym zapiszemy initrd.img oraz kernel. Użyłem dysku o pojemności 1 GB (wystarczającej dla tak małego systemu), na którym utworzyłem właśnie dwie partycje po 512 MB. Pierwsza partycja /boot ma oczywiście ustawioną boot flag.

Użycie polecenia fdisk.
Użycie polecenia fdisk.© AVLab

W pierwszej kolejności do wybranego katalogu montujemy partycję główną (tutaj /dev/sde2), a dalej tworzymy na niej katalog boot, do którego montujemy pierwszą partycję. Kopiujemy katalog z naszym systemem na dodaną partycję główną. Generujemy też od razu plik etc/fstab:

cat > linux/etc/fstab <<EOF

UUID="$(blkid -s UUID -o value /dev/sde2)" / ext4 defaults 0 1 

UUID="$(blkid -s UUID -o value /dev/sde1)" /boot ext4 defaults 0 1 

EOF

Musimy utworzyć plik initrd.img zawierający kolejny skrypt init, który po załadowaniu przez GRUB zamontuje dysk z systemem operacyjnym do tymczasowego katalogu, a następnie wykona (skądinąd bardzo ciekawe) polecenie switch_root uruchamiając napisany wcześniej skrypt init. Myślę, że w tym celu można wykorzystać istniejący katalog z systemem (którego zawartość skopiowaliśmy na /dev/sde2 i nie jej modyfikujemy w żaden sposób), bo w praktyce zmieniamy tylko jeden plik — init. Jego zawartość może ograniczyć się do tych kilku linii:

#!/bin/busybox sh

mkdir /mnt

mkdir /mnt/root

mkdir /proc

mkdir /sys

mount -t proc none /proc

mount -t sysfs none /sys

mount -t devtmpfs -o nosuid,mode=0755 udev /dev

modprobe ext4

mount -o rw /dev/sda2 /mnt/root

mount -o rw /dev/sda1 /mnt/root/boot

umount /proc

umount /sys

exec switch_root /mnt/root /bin/sh init

Budujemy initrd.img:

find . -print0 | cpio --null --create --format=newc > ../initrd.img

Pozostało nam dostosowanie katalogu boot. Kopiujemy do niego pliki powstałe w procesie kompilacji kernela, czyli vmlinuz, System.map, .config oraz initrd.img. Tworzymy dodatkowo podkatalog grub wraz z plikiem konfiguracyjnym grub.cfg:

set default=0

set timeout=5

set root=(hd0,1)

menuentry "BusyBox Linux" { 

linux /vmlinuz root=/dev/sda1 ro quiet

initrd /initrd.img

}

W GRUB menu (widocznym przez 5 sekund, chyba że użytkownik naciśnie Enter) zobaczymy wpis BusyBox Linux. Zostanie załadowany initrd.img, który wykona opisaną wyżej procedurę. /boot to osobna partycja, stąd konieczność podania ścieżek względnych. Na koniec uruchamiamy grub-install:

grub-install --target=i386-pc --boot-directory=boot /dev/sde

Podłączyłem dysk do innej maszyny i, jak widać, wszystkie etapy przebiegają zgodnie z założeniami.

GRUB menu.
GRUB menu.© AVLab

Zmieniłem hasło użytkownika root i dodałem drugiego użytkownika. Z jego poziomu utworzyłem plik skrypt.php wyświetlającego nazwę użytkownika, który go wykonał.

Dane są poprawnie zapisywane na dysku i zostają zachowane podczas restartów.

Dane zachowane przy restartach.
Dane zachowane przy restartach.© AVLab

VirtualBox posiada opcję nagrywania ekranu. Uznałem, że nagranie startu maszyny będzie odpowiednim dodatkiem do tego tekstu.

Podsumowanie

Mam nadzieję, że opis przygotowania własnego minimalnego systemu operacyjnego był interesującą lekturą. Utworzony system może stanowić bazę do dalszych modyfikacji i dodawania kolejnych funkcji.

Twórz treści i zarabiaj na ich publikacji. Dołącz do WP Kreatora

Programy

Zobacz więcej
Źródło artykułu:avlab.pl
Oceń jakość naszego artykułuTwoja opinia pozwala nam tworzyć lepsze treści.
Wybrane dla Ciebie
Komentarze (57)