pikvm / pi-builder

Extensible tool to build Arch Linux ARM for Raspberry Pi on x86_64 host using Docker
https://pikvm.org
GNU General Public License v3.0
96 stars 62 forks source link
archlinuxarm arm binfmt-misc build-environment build-system build-tool builder docker makefile pi-builder pi-kvm pikvm raspberry-pi raspberrypi sd-raspberry-pi

pi-builder

pi-builder is an easy-to-use and extendable tool to build Arch Linux ARM for Raspberry Pi using Docker.


Challenge

To build an OS, developers usually use a set of shell scripts, unique for each distribution. Those scripts create a chroot with necessary packages, edit configs, add users and so on. As a result, the system has the bare minimum to load, run and be further customised by the user.

However, when you create a product based on a single-board machine (a small router, an IP-camera, a smart home controller, etc), you might want to log all changes you made to the fresh OS to be able to repeat them without forgetting an important step like setting up sysctl.

A common solution is to create a large and horrifying shell script that executes all necessary actions either on the dev machine or the device itself. In case you use chroot and binfmt_misc or need to save intermediate changes, script complexity grows exponentially and it quickly becomes impossible to support.


What is pi-builder?

It's a new approach to target OS building on embedded devices. With pi-builder, you can build an image as if it was a simple Docker container rather than a real-world device OS. The build process is described using the default docker file syntax and it's executed in Docker on your dev machine. The resulting image can be exported to the SD card and loaded directly to Raspberry Pi.


Why pi-builder?


How does it work?

Arch Linux ARM (and other systems as well) comes in form of a minimal root file system you can install on and run from a flash drive. As those are regular roots, you can use them to create your own base Docker image using FROM scratch. This image, however, will contain executables and libraries for the ARM architecture, and if your machine is, eg., x86_64, none of the commands in this image will run.

The Linux kernel, however, has a special way to run binaries on a different architecture. You can configure binfmt_misc to run ARM binaries using an emulator (in this case, qemu-arm-static for x86_64 ). Pi-builder has a small script that sets up binfmt_misc on the host system to run ARM files.

In pi-builder, OS building is separated into stages, each of them being a different element of OS configuration. For example, the ro stage includes Dockerfile.part with all the necessary instructions and configs to create a read-only root. A watchdog stage has everything needed to set up a watchdog with optimal parameters on Raspberry Pi.

A full list of stages that come with pi-builder can be found here or below. You can choose the stages you need to set up your system and include them in your config. Stages are basically pieces of docker file that are combined in a specific order and executed during the build. You can also create your own stages by analogy.

Build sequence:

  1. pi-builder downloads statically compiled Debian qemu-arm-static and sets up binfmt_misc globally on your machine.
  2. The Arch Linux ARM image is downloaded and loaded into Docker as a base image.
  3. The container is build using the necessary stages -- package installation, configuration, cleanup, etc.
  4. You can run docker run (or make shell) in the resulting container to make sure everything's fine.
  5. Pi-builder's utility docker-extract extracts the container from Docker's internal storage and moves to the directory, making it an ordinary root file system.
  6. You can copy the resulting file system to the SD card and use it to load Raspberry Pi.

Usage

To build with pi-builder you need a fresh Docker that can run privileged containers (needed by auxilary image to install binfmt_misc, format the SD card and some other operations).

Pi-builder is configured by the main Makefile in the repository root. You can change parameters in the beginning, to do so create a file config.mk with new values. Default values are:

# Temporary images namespace, call in whatever you like
PROJECT ?= common

# Target Raspberry Pi platform
BOARD ?= rpi4

# List of necessary stages, more on it below
STAGES ?= __init__ os pikvm-repo watchdog no-bluetooth no-audit ro ssh-keygen __cleanup__

# Target system hostname
HOSTNAME ?= pi

# Target system locale (UTF-8)
LOCALE ?= en_US

# Target system timezone
TIMEZONE ?= Europe/Moscow

# Memory card location
CARD ?= /dev/mmcblk0

The most important parameters are BOARD (which board should the system be built for), STAGES (which stages should be included) and CARD (the SD card directory). You can change them by either passing new parameters when you run make, or by creating a config.mk with new values.

The __init__ stage must always be first: it has init instructions to create the base system image (FROM scratch). Stages that follow make the system "feel like home" -- by installing useful packages, setting up watchdog, making the system read-only, setting up root SSH keys and cleaning up temp files.

You can create your own stages and add them to the build alongside stock ones. To do so, create a directory for your stage in the stages folder and place the Dockerfile.part file there, similar to other stages. Alternatively, you can follow the same path as Pi-KVM (which was the first project pi-builder was made for).


Stock stages

Limitations


Some files, like /etc/host and /etc/hostname, are automatically filled by docker and all changes made from the docker file will be lost. For the hostname, there is a hack in the Makefile that writes the hostname to the exported system, or sets this name on make run. So in case you need to change something in those files, add it to the Makefile in a similar way.


TL;DR

How to build a system for Raspberry Pi 4 and install it to the SD card:

$ git clone https://github.com/pikvm/pi-builder
$ cd pi-builder
$ make rpi4
$ make install

How to build a system with your own stage list:

$ make os BOARD=rpi4 STAGES="__init__ os __cleanup__"

You can see other commands and current build config like so:

$ make

===== Available commands  =====
    make                # Print this help
    rpi2|rpi3|rpi4|zero2w  # Build Arch-ARM rootfs with pre-defined config
    make shell          # Run Arch-ARM shell
    make binfmt         # Before build
    make scan           # Find all RPi devices in the local network
    make clean          # Remove the generated rootfs
    make install         # Format /dev/mmcblk0 and flash the filesystem

===== Running configuration =====
    PROJECT = common
    BOARD   = rpi4
    STAGES  = __init__ os watchdog no-bluetooth ro ssh-keygen __cleanup__

    BUILD_OPTS =
    HOSTNAME   = pi
    LOCALE     = en_US
    TIMEZONE   = Europe/Moscow

    CARD = /dev/mmcblk0

    QEMU_RM     = 1

License

Copyright (C) 2018-2023 by Maxim Devaev mdevaev@gmail.com

This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.

This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.

You should have received a copy of the GNU General Public License along with this program. If not, see https://www.gnu.org/licenses/.