Dedykowaną dystrybucją Linuxa dla komputerków jedno płytkowych Raspberry Pi jest Raspbian (będący jedną z odmian Debiana). Dostarczany on jest typowo w postaci gotowych obrazów kart SD. Niestety nawet minimalistyczne obrazy Raspbiana zawierają często wiele zbędnych pakietów. Możliwe jest jednak łatwe przygotowanie własnego dedykowanego obrazu korzystając z instalacji w oparciu o debootstrap (dokładniej qemu-debootstrap).
Poniższy skrypt (build-rpi-image.sh) służy do automatycznego tworzenia takich obrazów.
Jedyną czynnością wymaganą do utworzenia obrazu (oprócz uruchomienia skryptu) jest wpisanie odpowiednich kluczy SSH do pliku authorized_keys
w utworzonym obrazie i/lub ustawienie hasła użytkownika.
Po nagraniu na kartę SD i zabootowaniu system będzie dostępny poprzez ssh z użyciem adresu IPv6 link-local (ustalanego jednoznacznie w oparciu o mac adres używanej płytki) i ustawianego klucza ssh.
Skrypt ten można potraktować też jako instruktarz tworzenia takich obrazów, należy zwrócić uwagę na:
- przygotowanie odpowiedniego układu partycji w obrazie (po wcześniejszym przygotowaniu pliku obrazu i jego zamapowaniu jako urządzenia
loop
) - instalacja odpowiedniego jądra i bootloadera (
raspberrypi-kernel
iraspberrypi-bootloader
) - utworzenie plików
cmdline.txt
iconfig.txt
na rozruchowej partycji vfat/boot
Więcej informacji o tworzeniu bootowalnych obrazów można znaleźć w artykule Własny Debian LiveUSB.
Tworzony obraz dedykowany jest dla Raspberry Pi w pierwszej wersji (dla innych odmian konieczne może być doinstalowanie dodatkowych pakietów z firmware) dostępnego jedynie poprzez SSH (dlatego zamiast ustawiania hasła skrypt przypomina o dodaniu kluczy SSH) i testowany był na „Raspberry Pi Model B rev. 2”.
Do automatycznej personalizacji tworzonego obrazu (np. ustawiania kluczy SSH, konfiguracji IP, etc) może posłużyć plik build-rpi-image.conf
i redefiniowana w nim funkcja configureLOCAL
. W pliku tym można tez zmienić wartości zmiennych konfiguracyjnych takich jak np. piuser
(określającej nazwę usera z sudo) hostname
(określającej nazwę hosta).
Skrypt wymaga zainstalowania pakietów: parted
qemu-user-static
binfmt-support
debootstrap
.
#!/bin/bash set -e # build config localconfig="build-rpi-image.conf" imgfile="rpi.img" mountdir="/mnt/rpi" logWrite() { echo "$@"; } waitBeforeUmount=true relesae="buster" mirror="http://archive.raspbian.org/raspbian/" bootsize=150M imagesize=1789 # local stuff config piuser="pi" hostname="rpi" configureLOCAL() { :; } # this settings can be overwrite in ${localconfig} file: if [ -f ${localconfig} ]; then logWrite "load LOCAL config from ${localconfig} file" . ${localconfig} else logWrite "can't find LOCAL config file: ${localconfig}" fi logWrite "create image file" dd if=/dev/zero of=${imgfile} bs=1M count=${imagesize} device=`losetup --partscan --find --show ${imgfile}` echo "create partitions in image file" parted ${device} "mklabel msdos" parted ${device} "mkpart primary fat32 1 ${bootsize}B" parted ${device} "mkpart primary ext4 ${bootsize}B 100%" logWrite "create filesystems" mkfs.vfat ${device}p1 mkfs.ext4 ${device}p2 logWrite "mount filesystems in ${mountdir}" mkdir -p ${mountdir} mount ${device}p2 ${mountdir} mkdir ${mountdir}/boot mount ${device}p1 ${mountdir}/boot logWrite "install ${relesae} via qemu-debootstrap" qemu-debootstrap --no-check-gpg --arch armhf ${relesae} ${mountdir} ${mirror} logWrite "configure apt and lang on target system" echo 'LANG="C.UTF-8"' > ${mountdir}/etc/default/locale; echo 'set -a; . /etc/default/locale; set +a' > ${mountdir}/etc/profile.d/locale.sh echo 'perl -e exit 2>&1 | grep "Setting locale failed" >/dev/null 2>&1 && export LC_ALL=C.UTF-8' >> ${mountdir}/etc/profile.d/locale.sh echo 'Apt::Install-Recommends "false";' > ${mountdir}/etc/apt/apt.conf.d/13norecommends logWrite "do general post install fix on target system" chroot ${mountdir} apt -y install aptitude vim xxd less wget screen tmux gawk sudo procps psmisc lsof picocom bash-completion \ openssh-server openssh-client sshguard ntpdate nftables curl python3 sendemail chroot ${mountdir} apt -y purge vim-tiny mawk nano tasksel tasksel-data paxctld libident debconf-i18n gdbm-l10n logWrite "install and configure R-PI stuff" echo "deb ${mirror} buster main contrib non-free rpi" > ${mountdir}/etc/apt/sources.list echo "deb http://archive.raspberrypi.org/debian/ buster main" >> ${mountdir}/etc/apt/sources.list wget 'http://archive.raspberrypi.org/debian/raspberrypi.gpg.key' -O - | chroot ${mountdir} apt-key add -q chroot ${mountdir} apt update chroot ${mountdir} apt -y install raspberrypi-bootloader raspberrypi-kernel i2c-tools chroot ${mountdir} apt clean cat > ${mountdir}/etc/fstab <<EOF /dev/mmcblk0p1 /boot/ vfat defaults 0 1 /dev/mmcblk0p2 / ext4 defaults,ro 0 1 EOF cat > ${mountdir}/boot/cmdline.txt <<EOF root=/dev/mmcblk0p2 rootfstype=ext4 fsck.repair=yes rootwait console=tty1 elevator=deadline dwc_otg.lpm_enable=0 EOF cat > ${mountdir}/boot/config.txt <<EOF # enable and configure i2c # dtparam=i2c_arm=on,i2c_arm_baudrate=400000 dtparam=i2c_arm=on dtoverlay=i2c-bcm2708 # # always start HDMI (even when monitor is not connected) # hdmi_force_hotplug=1 # # # get EDID info from /boot/edid.dat file (useful when monitor is not connected) # # file can be created via command: # # /opt/vc/bin/tvservice -d /boot/edid.dat # hdmi_edid_file=1 # # # disable overscan (black border of unused pixels) # disable_overscan=1 # for more options see http://elinux.org/RPi_config.txt # and https://github.com/raspberrypi/firmware/blob/master/boot/overlays/README EOF logWrite "move logs and tmp to /run" cat > ${mountdir}/etc/tmpfiles.d/on_tmpfs.conf <<EOF d /run/tmp 1777 root root - L+ /tmp - - - - /run/tmp L+ /var/tmp - - - - /run/tmp d /run/log 0755 root root - L+ /var/log - - - - /run/log d /run/dhcp 0755 root root - L+ /var/lib/dhcp - - - - /run/dhcp EOF rm -fr ${mountdir}/tmp/ ${mountdir}/var/tmp ${mountdir}/var/log/ ${mountdir}/var/lib/dhcp/ mkdir ${mountdir}/run/tmp ${mountdir}/run/log ${mountdir}/run/dhcp ln -s /run/tmp ${mountdir}/tmp; ln -s /run/tmp ${mountdir}/var/tmp; ln -s /run/log ${mountdir}/var/log; ln -s /run/dhcp ${mountdir}/var/lib/dhcp; sed -e 's@^.*Storage=.*@Storage=volatile@' -i ${mountdir}/etc/systemd/journald.conf logWrite "setup network and enable /etc/rc.local" cat > ${mountdir}/etc/rc.local <<EOF #!/bin/bash for i in \`ip l | awk '/^[0-9]/ {print \$2}' | tr -d :\`; do ifconfig \$i up ip link set dev \$i done EOF cat > ${mountdir}/etc/systemd/system/rc-local.service <<EOF [Unit] Description=/etc/rc.local ConditionPathExists=/etc/rc.local [Service] Type=forking ExecStart=/etc/rc.local start TimeoutSec=0 StandardOutput=tty RemainAfterExit=yes SysVStartPriority=99 [Install] WantedBy=multi-user.target EOF chmod +x ${mountdir}/etc/rc.local chroot ${mountdir} systemctl enable rc-local cat > ${mountdir}/etc/network/interfaces.d/lo0 <<EOF auto lo iface lo inet loopback EOF cat > ${mountdir}/etc/network/interfaces.d/lan0 <<EOF auto lan0 iface lan0 inet6 auto up /etc/network/firewall_ipv6.sh || true iface lan0 inet dhcp up /etc/network/firewall_ipv4.sh || true EOF cat > ${mountdir}/etc/network/firewall_ipv4.sh <<EOF #!/sbin/iptables-restore *filter :INPUT DROP :FORWARD ACCEPT :OUTPUT ACCEPT :sshguard - -A INPUT -i lo -j ACCEPT -A INPUT -m state --state ESTABLISHED -j ACCEPT -A INPUT -m state --state INVALID -j REJECT -A INPUT -p tcp --dport ssh -s 0.0.0.0/0 -j sshguard -A INPUT -p tcp --dport ssh -s 0.0.0.0/0 -j ACCEPT -A INPUT -p icmp --icmp-type timestamp-request -j REJECT -A INPUT -p icmp --icmp-type address-mask-request -j REJECT -A INPUT -p icmp -j ACCEPT -A INPUT -j REJECT COMMIT EOF cat > ${mountdir}/etc/network/firewall_ipv6.sh <<EOF #!/sbin/ip6tables-restore *filter :INPUT DROP :FORWARD ACCEPT :OUTPUT ACCEPT :sshguard - -A INPUT -i lo -j ACCEPT -A INPUT -m state --state ESTABLISHED -j ACCEPT -A INPUT -m state --state INVALID -j REJECT -A INPUT -p tcp --dport ssh -s fe80::/64 -j ACCEPT -A INPUT -p tcp --dport ssh -s ::/0 -j sshguard -A INPUT -p tcp --dport ssh -s ::/0 -j ACCEPT -A INPUT -p ipv6-icmp -j ACCEPT -A INPUT -j REJECT COMMIT EOF chmod +x ${mountdir}/etc/network/firewall_*.sh cat > ${mountdir}/etc/systemd/network/10-lan.link <<EOF [Match] Driver=smsc95xx #Property=ID_MODEL=ec00 ID_VENDOR=0424 [Link] NamePolicy= Name=lan0 EOF # create own /etc/systemd/network/99-default.link to disable # Debian overwrite of systemd rules for USB network devices # (/lib/udev/rules.d/73-usb-net-by-mac.rules) cat > ${mountdir}/etc/systemd/network/99-default.link <<EOF [Link] NamePolicy=keep kernel path database onboard slot mac MACAddressPolicy=persistent EOF echo '13 * * * * root /usr/bin/sleep 120 && /usr/sbin/ntpdate-debian >/dev/null 2>&1' > ${mountdir}/etc/cron.d/ntpdate logWrite "create admin user (${piuser}) and set hostname (${hostname})" chroot ${mountdir} adduser --disabled-password --gecos "" ${piuser} echo "${piuser} ALL=(ALL:ALL) NOPASSWD:ALL" > ${mountdir}/etc/sudoers.d/${piuser} mkdir ${mountdir}/home/${piuser}/.ssh touch ${mountdir}/home/${piuser}/.ssh/authorized_keys ${mountdir}/home/${piuser}/.bash_history chown -R ${piuser}:${piuser} ${mountdir}/home/${piuser}/.ssh ${mountdir}/home/${piuser}/.bash_history echo ${hostname} > ${mountdir}/etc/hostname logWrite "install LOCAL stuff" configureLOCAL if [ ! -s ${mountdir}/home/${piuser}/.ssh/authorized_keys ]; then logWrite "${mountdir}/home/${piuser}/.ssh/authorized_keys is empty !!!" logWrite "You can't login into r-pi system !!!" waitBeforeUmount=true fi if $waitBeforeUmount; then echo "You can personalize r-pi system via \`chroot ${mountdir}\` now" while read -p "Umount r-pi image? (y or yes): " f; do [ "$f" == "y" -o "$f" == "yes" ] && break; done fi logWrite "umount filesystems from image" umount ${mountdir}/boot umount ${mountdir} losetup -d ${device} logWrite "Image is ready in ${imgfile}" logWrite " use \`dd bs=10MB status=progress if=${imgfile} oflag=sync of=PATH_TO_SD_CARD\` to copy on SD card"