Compare commits

...

No commits in common. "main" and "master" have entirely different histories.
main ... master

21 changed files with 1188 additions and 8 deletions

26
.gitignore vendored Normal file
View File

@ -0,0 +1,26 @@
# suggested build result and log filenames
raspi*.bmap
raspi*.img
raspi*.img.xz
raspi*.log
raspi*.tar.gz
raspi*.sha256
raspi_base_bullseye.yaml
raspi_1_bullseye.yaml
raspi_2_bullseye.yaml
raspi_3_bullseye.yaml
raspi_4_bullseye.yaml
raspi_base_bookworm.yaml
raspi_1_bookworm.yaml
raspi_2_bookworm.yaml
raspi_3_bookworm.yaml
raspi_4_bookworm.yaml
raspi_base_trixie.yaml
raspi_1_trixie.yaml
raspi_2_trixie.yaml
raspi_3_trixie.yaml
raspi_4_trixie.yaml
*.log
*.img.xz
*.img.gz
*.img

28
LICENSE
View File

@ -1,11 +1,27 @@
Copyright (c) 2024 Hibbian.
Copyright © 2017, Michael Stapelberg and contributors
All rights reserved.
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
* Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
* Neither the name of Michael Stapelberg nor the
names of contributors may be used to endorse or promote products
derived from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
THIS SOFTWARE IS PROVIDED BY Michael Stapelberg ''AS IS'' AND ANY
EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL Michael Stapelberg BE LIABLE FOR ANY
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

75
Makefile Normal file
View File

@ -0,0 +1,75 @@
all: shasums
# List all the supported and built Pi platforms here. They get expanded
# to names like 'raspi_2_buster.yaml' and 'raspi_3_bullseye.img.xz'.
BUILD_FAMILIES := 1 2 3 4
BUILD_RELEASES := bullseye bookworm trixie
platforms := $(foreach plat, $(BUILD_FAMILIES),$(foreach rel, $(BUILD_RELEASES), raspi_$(plat)_$(rel)))
shasums: $(addsuffix .img.sha256,$(platforms)) $(addsuffix .img.xz.sha256,$(platforms))
xzimages: $(addsuffix .img.xz,$(platforms))
images: $(addsuffix .img,$(platforms))
yaml: $(addsuffix .yaml,$(platforms))
ifeq ($(shell id -u),0)
as_root =
else ifneq (,$(wildcard /usr/bin/fakemachine))
$(warning "This should normally be run as root, but found 'fakemachine', so using that.")
as_root = fakemachine -v $(CURDIR) -- env --chdir $(CURDIR)
else
$(error "This must be run as root")
endif
target_platforms:
@echo $(platforms)
# Generate targets based on all family * release combinations:
define dynamic_yaml_target =
raspi_$(1)_$(2).yaml: raspi_master.yaml generate-recipe.py
raspi_$(1)_$(2).yaml:
./generate-recipe.py $(1) $(2)
endef
$(foreach release,$(BUILD_RELEASES), \
$(foreach family,$(BUILD_FAMILIES), \
$(eval $(call dynamic_yaml_target,$(family),$(release)))))
%.img.sha256: %.img
echo $@
sha256sum $< > $@
%.img.xz.sha256: %.img.xz
echo $@
sha256sum $< > $@
%.img.xz: %.img
xz -f -k -z -9 $<
%.img.bmap: %.img
bmaptool create -o $@ $<
%.img: %.yaml
touch $(@:.img=.log)
time nice $(as_root) vmdb2 --verbose --rootfs-tarball=$(subst .img,.tar.gz,$@) --output=$@ $(subst .img,.yaml,$@) --log $(subst .img,.log,$@)
chmod 0644 $@ $(@,.img=.log)
_ck_root:
[ `whoami` = 'root' ] # Only root can summon vmdb2 ☹
_clean_yaml:
rm -f $(addsuffix .yaml,$(platforms)) raspi_base_bullseye.yaml raspi_base_bookworm.yaml raspi_base_trixie.yaml
_clean_images:
rm -f $(addsuffix .img,$(platforms))
_clean_xzimages:
rm -f $(addsuffix .img.xz,$(platforms))
_clean_bmaps:
rm -f $(addsuffix .img.bmap,$(platforms))
_clean_shasums:
rm -f $(addsuffix .img.sha256,$(platforms)) $(addsuffix .img.xz.sha256,$(platforms))
_clean_logs:
rm -f $(addsuffix .log,$(platforms))
_clean_tarballs:
rm -f $(addsuffix .tar.gz,$(platforms))
clean: _clean_xzimages _clean_images _clean_shasums _clean_yaml _clean_tarballs _clean_logs _clean_bmaps
.PHONY: _ck_root _build_img clean _clean_images _clean_yaml _clean_tarballs _clean_logs

135
README.md
View File

@ -1,3 +1,134 @@
# Raspi-image-spec
# Raspberry Pi image specs
Image specs for the raspi build.
This repository contains the files with which the images referenced at
https://wiki.debian.org/RaspberryPiImages have been built.
## Option 1: Downloading an image
See https://wiki.debian.org/RaspberryPiImages for where to obtain the
latest pre-built image.
## Option 2: Building your own image
If you prefer, you can build a Debian Raspberry Pi image
yourself. If you are reading this document online, you should first
clone this repository:
```shell
git clone --recursive https://salsa.debian.org/raspi-team/image-specs.git
cd image-specs
```
For this you will first need to install the following packages on a
Debian Bullseye (11) or higher system:
* binfmt-support
* bmap-tools
* debootstrap
* dosfstools
* fakemachine (optional, only available on amd64)
* kpartx
* qemu-utils
* qemu-user-static
* time
* vmdb2 (>= 0.17)
* python3
* zerofree (because of [#1021341](https://bugs.debian.org/1021341))
To install these (as root):
```shell
apt install -y vmdb2 dosfstools qemu-utils qemu-user-static debootstrap binfmt-support time kpartx bmap-tools python3 zerofree
apt install -y fakemachine
```
If debootstrap still fails with exec format error, try
running `dpkg-reconfigure qemu-user-static`. This calls
`/var/lib/dpkg/info/qemu-user-static.postinst` which uses binfmt-support
to register the executable format with /usr/bin/qemu-$fmt-static
This repository includes a master YAML recipe (which is basically a
configuration file) for all of the generated images, diverting as
little as possible in a parametrized way. The master recipe is
[raspi_master.yaml](raspi_master.yaml).
A Makefile is supplied to drive the build of the recipes into images.
If `fakemachine` is installed, it can be run as an unprivileged user.
Otherwise, because some steps of building the image require root privileges,
you'll need to execute `make` as root.
The argument to `make` is constructed as follows:
`raspi_<model>_<release>.<result-type>`
Whereby <model\> is one of `1`, `2`, `3` or `4`, <release\> is either
`bullseye`, `bookworm`, or `trixie`; and <result-type\> is `img` or `yaml`.
Model `1` should be used for the Raspberry Pi 0, 0w and 1, models A and
B. Model `2` for the Raspberry Pi 2 models A and B. Model `3` for all
models of the Raspberry Pi 3 and model `4` for all models of the
Raspberry Pi 4.
So if you want to build the default image for a Raspberry Pi 3B+ with
Bullseye, you can just issue:
```shell
make raspi_3_bullseye.img
```
This will first create a `raspi_3_bullseye.yaml` file and then use that
*yaml* recipe to build the image with `vmdb2`.
You can also edit the `yaml` file to customize the built image. If you
want to start from the platform-specific recipe, you can issue:
```shell
make raspi_3_bullseye.yaml
```
The recipe drives [vmdb2](https://vmdb2.liw.fi/), the successor to
`vmdebootstrap`. Please refer to [its
documentation](https://vmdb2.liw.fi/documentation/) for further details;
it is quite an easy format to understand.
Copy the generated file to a name descriptive enough for you (say,
`my_raspi_bullseye.yaml`). Once you have edited the recipe for your
specific needs, you can generate the image by issuing the following (as
root):
```shell
vmdb2 --rootfs-tarball=my_raspi_bullseye.tar.gz --output \
my_raspi_bullseye.img my_raspi_bullseye.yaml --log my_raspi_bullseye.log
```
This is, just follow what is done by the `_build_img` target of the
Makefile.
## Installing the image onto the Raspberry Pi
Plug an SD card which you would like to entirely overwrite into your SD card reader.
Assuming your SD card reader provides the device `/dev/mmcblk0`
(**Beware** If you choose the wrong device, you might overwrite
important parts of your system. Double check it's the correct
device!), copy the image onto the SD card:
```shell
bmaptool copy raspi_3_bullseye.img.xz /dev/mmcblk0
```
Alternatively, if you don't have `bmap-tools` installed, you can use
`dd` with the compressed image:
```shell
xzcat raspi_3_bullseye.img.xz | dd of=/dev/mmcblk0 bs=64k oflag=dsync status=progress
```
Or with the uncompressed image:
```shell
dd if=raspi_3_bullseye.img of=/dev/mmcblk0 bs=64k oflag=dsync status=progress
```
Then, plug the SD card into the Raspberry Pi, and power it up.
The image uses the hostname `rpi0w`, `rpi2`, `rpi3`, or `rpi4` depending on the
target build. The provided image will allow you to log in with the
`root` account with no password set, but only logging in at the
physical console (be it serial or by USB keyboard and HDMI monitor).

43
debian/salsa-ci.yml vendored Normal file
View File

@ -0,0 +1,43 @@
---
stages:
# Garbage in is garbage out, so verify our input
- check input
- build
variables:
DEBIAN_FRONTEND: "noninteractive"
# At https://salsa.debian.org/salsa-ci-team/pipeline/container_registry one can see which images are available
SALSA_CI_IMAGES: registry.salsa.debian.org/salsa-ci-team/pipeline
BASE_CI_IMAGES: ${SALSA_CI_IMAGES}/base
yamllint:
stage: check input
image: $BASE_CI_IMAGES:unstable
dependencies: []
script:
- apt-get update && apt-get upgrade -y
- apt-get install -y yamllint
- yamllint -c debian/yamllint.yml .
shellcheck:
stage: check input
image: $BASE_CI_IMAGES:unstable
dependencies: []
script:
- apt-get update && apt-get upgrade -y
- apt-get install -y shellcheck
- shellcheck -e SC1090,SC1091 -s dash $(find rootfs/etc/initramfs-tools -type f -executable | xargs grep -l '^#!/bin/sh')
build yamls:
stage: build
image: $BASE_CI_IMAGES:unstable
dependencies: []
script:
- apt-get update && apt-get upgrade -y
- apt-get install -y python3 make git
- make yaml
- mkdir build
- cp raspi*.yaml build/
artifacts:
paths:
- build/

6
debian/yamllint.yml vendored Normal file
View File

@ -0,0 +1,6 @@
---
extends: default
rules:
line-length: disable

Binary file not shown.

BIN
etc/apt/trusted.gpg.d/htop Normal file

Binary file not shown.

157
generate-recipe.py Executable file
View File

@ -0,0 +1,157 @@
#!/usr/bin/python3
import re
import sys
import subprocess
# pylint: disable=invalid-name
### Sanity/usage checks
if len(sys.argv) != 3:
print("E: need 2 arguments", file=sys.stderr)
sys.exit(1)
version = sys.argv[1]
if version not in ["1", "2", "3", "4"]:
print("E: unsupported version %s" % version, file=sys.stderr)
sys.exit(1)
suite = sys.argv[2]
if suite not in ['bullseye', 'bookworm', 'trixie']:
print("E: unsupported suite %s" % suite, file=sys.stderr)
sys.exit(1)
target_yaml = 'raspi_%s_%s.yaml' % (version, suite)
### Setting variables based on suite and version starts here
# Arch, kernel, DTB:
if version == '1':
arch = 'armel'
linux = 'linux-image-rpi'
dtb = '/usr/lib/linux-image-*-rpi/bcm*rpi-*.dtb'
elif version == '2':
arch = 'armhf'
linux = 'linux-image-armmp'
dtb = '/usr/lib/linux-image-*-armmp/bcm*rpi*.dtb'
elif version in ['3', '4']:
arch = 'arm64'
linux = 'linux-image-arm64'
dtb = '/usr/lib/linux-image-*-arm64/broadcom/bcm*rpi*.dtb'
# Bookworm introduced the 'non-free-firmware' component¹; before that,
# raspi-firmware was in 'non-free'
#
# ¹ https://www.debian.org/vote/2022/vote_003
if suite != 'bullseye':
firmware_component = 'non-free-firmware'
firmware_component_old = 'non-free'
else:
firmware_component = 'non-free'
firmware_component_old = ''
# wireless firmware:
if version != '2':
wireless_firmware = 'firmware-brcm80211'
else:
wireless_firmware = ''
# bluetooth firmware:
if version != '2':
bluetooth_firmware = 'bluez-firmware'
else:
bluetooth_firmware = ''
# Pi 4 on buster required some backports. Let's keep variables around, ready to
# be used whenever we need to pull specific things from backports.
backports_enable = False
backports_suite = '%s-backports' % suite
# Serial console:
if version in ['1', '2']:
serial = 'ttyAMA0,115200'
elif version in ['3', '4']:
serial = 'ttyS1,115200'
# CMA fixup:
extra_chroot_shell_cmds = []
if version == '4':
extra_chroot_shell_cmds = [
"sed -i 's/cma=64M //' /boot/firmware/cmdline.txt",
]
# Hostname:
hostname = 'rpi_%s' % version
# Nothing yet!
extra_root_shell_cmds = []
### The following prepares substitutions based on variables set earlier
# Enable backports with a reason, or add commented-out entry:
if backports_enable:
backports_stanza = """
%s
deb http://deb.debian.org/debian/ %s main %s
""" % (backports_enable, backports_suite, firmware_component)
else:
# ugh
backports_stanza = """
# Backports are _not_ enabled by default.
# Enable them by uncommenting the following line:
# deb http://deb.debian.org/debian %s main %s
""" % (backports_suite, firmware_component)
gitcommit = subprocess.getoutput("git show -s --pretty='format:%C(auto)%h (%s, %ad)' --date=short ")
buildtime = subprocess.getoutput("date --utc +'%Y-%m-%d %H:%M'")
### Write results:
def align_replace(text, pattern, replacement):
"""
This helper lets us keep the indentation of the matched pattern
with the upcoming replacement, across multiple lines. Naive
implementation, please make it more pythonic!
"""
lines = text.splitlines()
for i, line in enumerate(lines):
m = re.match(r'^(\s+)%s' % pattern, line)
if m:
indent = m.group(1)
del lines[i]
for r in replacement:
lines.insert(i, '%s%s' % (indent, r))
i = i + 1
break
return '\n'. join(lines) + '\n'
with open('raspi_master.yaml', 'r') as in_file:
with open(target_yaml, 'w') as out_file:
in_text = in_file.read()
out_text = in_text \
.replace('__RELEASE__', suite) \
.replace('__ARCH__', arch) \
.replace('__FIRMWARE_COMPONENT__', firmware_component) \
.replace('__FIRMWARE_COMPONENT_OLD__', firmware_component_old) \
.replace('__LINUX_IMAGE__', linux) \
.replace('__DTB__', dtb) \
.replace('__WIRELESS_FIRMWARE__', wireless_firmware) \
.replace('__BLUETOOTH_FIRMWARE__', bluetooth_firmware) \
.replace('__SERIAL_CONSOLE__', serial) \
.replace('__HOST__', hostname) \
.replace('__GITCOMMIT__', gitcommit) \
.replace('__BUILDTIME__', buildtime)
out_text = align_replace(out_text, '__EXTRA_ROOT_SHELL_CMDS__', extra_root_shell_cmds)
out_text = align_replace(out_text, '__EXTRA_CHROOT_SHELL_CMDS__', extra_chroot_shell_cmds)
out_text = align_replace(out_text, '__BACKPORTS__', backports_stanza.splitlines())
# Try not to keep lines where the placeholder was replaced
# with nothing at all (including on a "list item" line):
filtered = [x for x in out_text.splitlines()
if not re.match(r'^\s+$', x)
and not re.match(r'^\s+-\s*$', x)]
out_file.write('\n'.join(filtered) + "\n")

199
raspi_4_hibbian.yaml Normal file
View File

@ -0,0 +1,199 @@
---
# See https://wiki.debian.org/RaspberryPi3 for known issues and more details.
# image.yml based on revision: ff7fdbf (Switch from qemu-debootstrap to debootstrap., 2024-01-01)
steps:
- mkimg: "{{ output }}"
size: 2500M
- mklabel: msdos
device: "{{ output }}"
- mkpart: primary
fs-type: 'fat32'
device: "{{ output }}"
start: 4MiB
end: 512MiB
tag: tag-firmware
- mkpart: primary
device: "{{ output }}"
start: 512MiB
end: 100%
tag: tag-root
- kpartx: "{{ output }}"
- mkfs: vfat
partition: tag-firmware
label: RASPIFIRM
- mkfs: ext4
partition: tag-root
label: RASPIROOT
- mount: tag-root
- mount: tag-firmware
mount-on: tag-root
dirname: '/boot/firmware'
- unpack-rootfs: tag-root
- debootstrap: bookworm
require_empty_target: false
mirror: http://deb.debian.org/debian
target: tag-root
arch: arm64
components:
- main
- non-free-firmware
- non-free
unless: rootfs_unpacked
- create-file: /etc/apt/sources.list
contents: |+
deb http://deb.debian.org/debian bookworm main non-free-firmware non-free
deb http://deb.debian.org/debian bookworm-updates main non-free-firmware non-free
deb http://security.debian.org/debian-security bookworm-security main non-free-firmware non-free
# Backports are _not_ enabled by default.
# Enable them by uncommenting the following line:
deb http://deb.debian.org/debian bookworm-backports main non-free-firmware
- create-file: /etc/apt/preferences.d/hibbian.pref
contents: |+
Package: *
Pin: release o=Hibbian
Pin-Priority: 600
- create-file: /etc/apt/preferences.d/hibbian.pref
contents: |+
Package: linux-image
Pin: release o=Debian Backports
Pin-Priority: 500
- create-file: /etc/apt/sources.list.d/hibbian.list
contents: |+
deb http://repo.hibbian.org/hibbian bookworm-hibbian-unstable main non-free-firmware non-free
- copy-file: /etc/apt/trusted.gpg.d/hibbian-archive-keyring.gpg
src: etc/apt/trusted.gpg.d/hibbian-archive-keyring.gpg
perm: 0755
- copy-file: /etc/initramfs-tools/hooks/rpi-resizerootfs
src: rootfs/etc/initramfs-tools/hooks/rpi-resizerootfs
perm: 0755
unless: rootfs_unpacked
- copy-file: /etc/initramfs-tools/scripts/local-bottom/rpi-resizerootfs
src: rootfs/etc/initramfs-tools/scripts/local-bottom/rpi-resizerootfs
perm: 0755
unless: rootfs_unpacked
- apt: install
packages:
- ca-certificates
- dosfstools
- iw
- parted
- openssh-server
- network-manager
- systemd-timesyncd
- linux-image-arm64
- raspi-firmware
- firmware-brcm80211
- bluez-firmware
- base-files
- linbpq
- sudo
- vim-nox
- net-tools
tag: tag-root
- cache-rootfs: tag-root
unless: rootfs_unpacked
- shell: |
echo "hibbian-$(date +%Y%m%d)" > "${ROOT?}/etc/hostname"
# Allow root logins locally with no password
sed -i 's,root:[^:]*:,root::,' "${ROOT?}/etc/shadow"
install -m 644 -o root -g root rootfs/etc/fstab "${ROOT?}/etc/fstab"
install -m 644 -o root -g root rootfs/etc/network/interfaces.d/eth0 "${ROOT?}/etc/network/interfaces.d/eth0"
install -m 600 -o root -g root rootfs/etc/network/interfaces.d/wlan0 "${ROOT?}/etc/network/interfaces.d/wlan0"
install -m 755 -o root -g root rootfs/usr/local/sbin/rpi-set-sysconf "${ROOT?}/usr/local/sbin/rpi-set-sysconf"
install -m 644 -o root -g root rootfs/etc/systemd/system/rpi-set-sysconf.service "${ROOT?}/etc/systemd/system/"
install -m 644 -o root -g root rootfs/boot/firmware/sysconf.txt "${ROOT?}/boot/firmware/sysconf.txt"
mkdir -p "${ROOT?}/etc/systemd/system/basic.target.requires/"
ln -s /etc/systemd/system/rpi-set-sysconf.service "${ROOT?}/etc/systemd/system/basic.target.requires/rpi-set-sysconf.service"
# Resize script is now in the initrd for first boot; no need to ship it.
rm -f "${ROOT?}/etc/initramfs-tools/hooks/rpi-resizerootfs"
rm -f "${ROOT?}/etc/initramfs-tools/scripts/local-bottom/rpi-resizerootfs"
install -m 644 -o root -g root rootfs/etc/systemd/system/rpi-reconfigure-raspi-firmware.service "${ROOT?}/etc/systemd/system/"
mkdir -p "${ROOT?}/etc/systemd/system/multi-user.target.requires/"
ln -s /etc/systemd/system/rpi-reconfigure-raspi-firmware.service "${ROOT?}/etc/systemd/system/multi-user.target.requires/rpi-reconfigure-raspi-firmware.service"
install -m 644 -o root -g root rootfs/etc/systemd/system/rpi-generate-ssh-host-keys.service "${ROOT?}/etc/systemd/system/"
ln -s /etc/systemd/system/rpi-generate-ssh-host-keys.service "${ROOT?}/etc/systemd/system/multi-user.target.requires/rpi-generate-ssh-host-keys.service"
rm -f "${ROOT?}"/etc/ssh/ssh_host_*_key*
root-fs: tag-root
# Copy the relevant device tree files to the boot partition
- chroot: tag-root
shell: |
install -m 644 -o root -g root /usr/lib/linux-image-*-arm64/broadcom/bcm*rpi*.dtb /boot/firmware/
# Clean up archive cache (likely not useful) and lists (likely outdated) to
# reduce image size by several hundred megabytes.
- chroot: tag-root
shell: |
apt-get clean
rm -rf /var/lib/apt/lists
# Modify the kernel commandline we take from the firmware to boot from
# the partition labeled raspiroot instead of forcing it to mmcblk0p2.
# Also insert the serial console right before the root= parameter.
#
# These changes will be overwritten after the hardware is probed
# after dpkg reconfigures raspi-firmware (upon first boot), so make
# sure we don't lose label-based booting.
- chroot: tag-root
shell: |
sed -i 's/root=/console=ttyS1,115200 root=/' /boot/firmware/cmdline.txt
sed -i 's#root=/dev/mmcblk0p2#root=LABEL=RASPIROOT#' /boot/firmware/cmdline.txt
sed -i 's/^#ROOTPART=.*/ROOTPART=LABEL=RASPIROOT/' /etc/default/raspi*-firmware
sed -i 's/cma=64M //' /boot/firmware/cmdline.txt
# TODO(https://github.com/larswirzenius/vmdb2/issues/24): remove once vmdb
# clears /etc/resolv.conf on its own.
- shell: |
rm "${ROOT?}/etc/resolv.conf"
root-fs: tag-root
# Clear /etc/machine-id and /var/lib/dbus/machine-id, as both should
# be auto-generated upon first boot. From the manpage
# (machine-id(5)):
#
# For normal operating system installations, where a custom image is
# created for a specific machine, /etc/machine-id should be
# populated during installation.
#
# Note this will also trigger ConditionFirstBoot=yes for systemd.
# On Buster, /etc/machine-id should be an emtpy file, not an absent file
# On Bullseye, /etc/machine-id should not exist in an image
- chroot: tag-root
shell: |
rm -f /etc/machine-id /var/lib/dbus/machine-id
echo "uninitialized" > /etc/machine-id
# Create /etc/raspi-image-id to know, from what commit the image was built
- chroot: tag-root
shell: |
echo "image based on revision: ff7fdbf (Switch from qemu-debootstrap to debootstrap., 2024-01-01) and built in 2024 with love from Hibby" > "/etc/raspi-image-id"

176
raspi_master.yaml Normal file
View File

@ -0,0 +1,176 @@
---
# See https://wiki.debian.org/RaspberryPi3 for known issues and more details.
# image.yml based on revision: __GITCOMMIT__
steps:
- mkimg: "{{ output }}"
size: 2500M
- mklabel: msdos
device: "{{ output }}"
- mkpart: primary
fs-type: 'fat32'
device: "{{ output }}"
start: 4MiB
end: 512MiB
tag: tag-firmware
- mkpart: primary
device: "{{ output }}"
start: 512MiB
end: 100%
tag: tag-root
- kpartx: "{{ output }}"
- mkfs: vfat
partition: tag-firmware
label: RASPIFIRM
- mkfs: ext4
partition: tag-root
label: RASPIROOT
- mount: tag-root
- mount: tag-firmware
mount-on: tag-root
dirname: '/boot/firmware'
- unpack-rootfs: tag-root
- debootstrap: __RELEASE__
require_empty_target: false
mirror: http://deb.debian.org/debian
target: tag-root
arch: __ARCH__
components:
- main
- __FIRMWARE_COMPONENT__
- __FIRMWARE_COMPONENT_OLD__
unless: rootfs_unpacked
- create-file: /etc/apt/sources.list
contents: |+
deb http://deb.debian.org/debian __RELEASE__ main __FIRMWARE_COMPONENT__ __FIRMWARE_COMPONENT_OLD__
deb http://deb.debian.org/debian __RELEASE__-updates main __FIRMWARE_COMPONENT__ __FIRMWARE_COMPONENT_OLD__
deb http://security.debian.org/debian-security __RELEASE__-security main __FIRMWARE_COMPONENT__ __FIRMWARE_COMPONENT_OLD__
__BACKPORTS__
unless: rootfs_unpacked
- copy-file: /etc/initramfs-tools/hooks/rpi-resizerootfs
src: rootfs/etc/initramfs-tools/hooks/rpi-resizerootfs
perm: 0755
unless: rootfs_unpacked
- copy-file: /etc/initramfs-tools/scripts/local-bottom/rpi-resizerootfs
src: rootfs/etc/initramfs-tools/scripts/local-bottom/rpi-resizerootfs
perm: 0755
unless: rootfs_unpacked
- apt: install
packages:
- ca-certificates
- dosfstools
- iw
- parted
- ssh
- wpasupplicant
- systemd-timesyncd
- __LINUX_IMAGE__
- raspi-firmware
- __WIRELESS_FIRMWARE__
- __BLUETOOTH_FIRMWARE__
tag: tag-root
unless: rootfs_unpacked
- cache-rootfs: tag-root
unless: rootfs_unpacked
- shell: |
echo "__HOST__-$(date +%Y%m%d)" > "${ROOT?}/etc/hostname"
# Allow root logins locally with no password
sed -i 's,root:[^:]*:,root::,' "${ROOT?}/etc/shadow"
install -m 644 -o root -g root rootfs/etc/fstab "${ROOT?}/etc/fstab"
install -m 644 -o root -g root rootfs/etc/network/interfaces.d/eth0 "${ROOT?}/etc/network/interfaces.d/eth0"
install -m 600 -o root -g root rootfs/etc/network/interfaces.d/wlan0 "${ROOT?}/etc/network/interfaces.d/wlan0"
install -m 755 -o root -g root rootfs/usr/local/sbin/rpi-set-sysconf "${ROOT?}/usr/local/sbin/rpi-set-sysconf"
install -m 644 -o root -g root rootfs/etc/systemd/system/rpi-set-sysconf.service "${ROOT?}/etc/systemd/system/"
install -m 644 -o root -g root rootfs/boot/firmware/sysconf.txt "${ROOT?}/boot/firmware/sysconf.txt"
mkdir -p "${ROOT?}/etc/systemd/system/basic.target.requires/"
ln -s /etc/systemd/system/rpi-set-sysconf.service "${ROOT?}/etc/systemd/system/basic.target.requires/rpi-set-sysconf.service"
# Resize script is now in the initrd for first boot; no need to ship it.
rm -f "${ROOT?}/etc/initramfs-tools/hooks/rpi-resizerootfs"
rm -f "${ROOT?}/etc/initramfs-tools/scripts/local-bottom/rpi-resizerootfs"
install -m 644 -o root -g root rootfs/etc/systemd/system/rpi-reconfigure-raspi-firmware.service "${ROOT?}/etc/systemd/system/"
mkdir -p "${ROOT?}/etc/systemd/system/multi-user.target.requires/"
ln -s /etc/systemd/system/rpi-reconfigure-raspi-firmware.service "${ROOT?}/etc/systemd/system/multi-user.target.requires/rpi-reconfigure-raspi-firmware.service"
install -m 644 -o root -g root rootfs/etc/systemd/system/rpi-generate-ssh-host-keys.service "${ROOT?}/etc/systemd/system/"
ln -s /etc/systemd/system/rpi-generate-ssh-host-keys.service "${ROOT?}/etc/systemd/system/multi-user.target.requires/rpi-generate-ssh-host-keys.service"
rm -f "${ROOT?}"/etc/ssh/ssh_host_*_key*
__EXTRA_ROOT_SHELL_CMDS__
root-fs: tag-root
# Copy the relevant device tree files to the boot partition
- chroot: tag-root
shell: |
install -m 644 -o root -g root __DTB__ /boot/firmware/
# Clean up archive cache (likely not useful) and lists (likely outdated) to
# reduce image size by several hundred megabytes.
- chroot: tag-root
shell: |
apt-get clean
rm -rf /var/lib/apt/lists
# Modify the kernel commandline we take from the firmware to boot from
# the partition labeled raspiroot instead of forcing it to mmcblk0p2.
# Also insert the serial console right before the root= parameter.
#
# These changes will be overwritten after the hardware is probed
# after dpkg reconfigures raspi-firmware (upon first boot), so make
# sure we don't lose label-based booting.
- chroot: tag-root
shell: |
sed -i 's/root=/console=__SERIAL_CONSOLE__ root=/' /boot/firmware/cmdline.txt
sed -i 's#root=/dev/mmcblk0p2#root=LABEL=RASPIROOT#' /boot/firmware/cmdline.txt
sed -i 's/^#ROOTPART=.*/ROOTPART=LABEL=RASPIROOT/' /etc/default/raspi*-firmware
__EXTRA_CHROOT_SHELL_CMDS__
# TODO(https://github.com/larswirzenius/vmdb2/issues/24): remove once vmdb
# clears /etc/resolv.conf on its own.
- shell: |
rm "${ROOT?}/etc/resolv.conf"
root-fs: tag-root
# Clear /etc/machine-id and /var/lib/dbus/machine-id, as both should
# be auto-generated upon first boot. From the manpage
# (machine-id(5)):
#
# For normal operating system installations, where a custom image is
# created for a specific machine, /etc/machine-id should be
# populated during installation.
#
# Note this will also trigger ConditionFirstBoot=yes for systemd.
# On Buster, /etc/machine-id should be an emtpy file, not an absent file
# On Bullseye, /etc/machine-id should not exist in an image
- chroot: tag-root
shell: |
rm -f /etc/machine-id /var/lib/dbus/machine-id
echo "uninitialized" > /etc/machine-id
# Create /etc/raspi-image-id to know, from what commit the image was built
- chroot: tag-root
shell: |
echo "image based on revision: __GITCOMMIT__ and build on __BUILDTIME__ (UTC)" > "/etc/raspi-image-id"

View File

@ -0,0 +1,35 @@
# This file will be automatically evaluated and installed at next boot
# time, and regenerated (to avoid leaking passwords and such information).
#
# To force it to be evaluated immediately, you can run (as root):
#
# /usr/sbin/rpi-set-sysconf
#
# You can disable the file evaluation by disabling the rpi-set-sysconf
# service in systemd:
#
# systemctl disable rpi-set-sysconf
#
# Comments (all portions of a line following a '#' character) are
# ignored. This file is read line by line. Valid
# configuration lines are of the form 'key=value'. Whitespace around
# 'key' and 'value' is ignored. This file will be _regenerated_ every
# time it is evaluated.
#
# We follow the convention to indent with one space comments, and
# leave no space to indicate the line is an example that could be
# uncommented.
# root_pw - Set a password for the root user (by default, it allows
# for a passwordless login)
#root_pw=FooBar
# root_authorized_key - Set an authorized key for a root ssh login
#root_authorized_key=
# hostname - Set the system hostname.
#hostname=rpi
# We found the following unhandled keys - That means, the
# configuration script does not know how to handle them. Please
# double-check them!

4
rootfs/etc/fstab Normal file
View File

@ -0,0 +1,4 @@
# The root file system has fs_passno=1 as per fstab(5) for automatic fsck.
LABEL=RASPIROOT / ext4 rw 0 1
# All other file systems have fs_passno=2 as per fstab(5) for automatic fsck.
LABEL=RASPIFIRM /boot/firmware vfat rw 0 2

View File

@ -0,0 +1,52 @@
#!/bin/sh
set -e
#
# List the soft prerequisites here. This is a space separated list of
# names, of scripts that are in the same directory as this one, that
# must be run before this one can be.
#
PREREQS=""
case $1 in
prereqs) echo "$PREREQS"; exit 0;;
esac
. /usr/share/initramfs-tools/hook-functions
# List ALL the programs we need, because we explicitly call them
# Don't *assume* it will be included!
# The update-initramfs script will figure out any dependencies
# that also need to be included, so lets not do that
#
# Find the path as used by the package itself; usrmerge may not be used
# from coreutils
copy_exec /usr/bin/realpath
copy_exec /usr/bin/tail
copy_exec /usr/bin/test
# from dosfstools
copy_exec /sbin/fsck.vfat
# from e2fsprogs
copy_exec /sbin/resize2fs
copy_exec /sbin/fsck.ext4
# from grep
copy_exec /bin/grep
# from logsave
copy_exec /sbin/logsave
# from mount
copy_exec /bin/mount
copy_exec /bin/umount
# from parted
copy_exec /sbin/parted
copy_exec /sbin/partprobe
# from util-linux
copy_exec /bin/lsblk
copy_exec /sbin/blkid
copy_exec /sbin/fsck

View File

@ -0,0 +1,59 @@
#!/bin/sh
set -e
#
# List the soft prerequisites here. This is a space separated list of
# names, of scripts that are in the same directory as this one, that
# must be run before this one can be.
#
PREREQS=""
case $1 in
prereqs) echo "$PREREQS"; exit 0;;
esac
. /scripts/functions
# Given the root partition, get the underlying device and partition number
rootpart=$(realpath "$ROOT")
rootpart_nr=$(blkid -sPART_ENTRY_NUMBER -o value -p "$rootpart")
rootdev="/dev/$(lsblk -no pkname "$rootpart")"
# Parted will detect if the GPT label is messed up and fix it
# automatically, we just need to tell it to do so.
parted -s "$rootdev" print 2>&1 | grep -z "fix the GPT" && {
echo "Fix" | parted ---pretend-input-tty "$rootdev" print
}
# Check if there's free space at the end of the device
free_space="$(parted -m -s "$rootdev" print free | tail -n1 | grep free)"
if test -z "$free_space"; then
# Great, we already resized; nothing left to do!
exit 0
fi
log_begin_msg "$0 resizing $ROOT"
# Unmount for safety; fail if unset or empty (shellcheck SC2154)
umount "${rootmnt:?}"
# Expand the partition size to fill the entire device
parted -s "$rootdev" resizepart "$rootpart_nr" 100%
wait_for_udev 5
# Now resize the filesystem
partprobe "$rootdev"
resize2fs "$rootpart"
# After resizing, (re)check the root partition's filesystem
fsck "$rootpart"
# Remount root
# Don't quote ${ROOTFLAGS} as that results in an extra (empty) argument
# to 'mount', which in turn causes a boot failure
# shellcheck disable=SC2086
if ! mount -r ${FSTYPE:+-t "${FSTYPE}"} ${ROOTFLAGS} "${ROOT}" "${rootmnt?}"; then
panic "Failed to mount ${ROOT} as root file system."
fi
log_end_msg

View File

@ -0,0 +1,3 @@
auto eth0
iface eth0 inet dhcp
iface eth0 inet6 auto

View File

@ -0,0 +1,8 @@
# To enable wireless networking, uncomment the following lines and -naturally-
# replace with your network's details.
#
# allow-hotplug wlan0
# iface wlan0 inet dhcp
# iface wlan0 inet6 dhcp
# wpa-ssid my-network-ssid
# wpa-psk s3kr3t_P4ss

View File

@ -0,0 +1,10 @@
[Unit]
Description=generate SSH host keys
ConditionPathExistsGlob=!/etc/ssh/ssh_host_*_key
[Service]
Type=oneshot
ExecStart=/usr/sbin/dpkg-reconfigure -fnoninteractive openssh-server
[Install]
RequiredBy=multi-user.target

View File

@ -0,0 +1,14 @@
[Unit]
Description=Reconfigure raspi-firmware to regenerate config.txt matching actual hardware
Before=sysinit.target
DefaultDependencies=no
RequiresMountsFor=/boot/firmware
[Service]
Type=oneshot
TimeoutSec=infinity
ExecStart=/usr/sbin/dpkg-reconfigure raspi-firmware
ExecStart=/bin/systemctl --no-reload disable %n
[Install]
RequiredBy=sysinit.target

View File

@ -0,0 +1,9 @@
[Unit]
Description=Set up system configuration
[Service]
Type=oneshot
ExecStart=/usr/local/sbin/rpi-set-sysconf
[Install]
RequiredBy=basic.target

View File

@ -0,0 +1,157 @@
#!/usr/bin/perl
use strict;
use warnings;
use IO::File;
use IO::Pipe;
use feature 'switch';
my ($filename, $conf);
$filename = '/boot/firmware/sysconf.txt';
logger('info', "Reading the system configuration settings from $filename");
$conf = read_conf($filename);
if (my $pass = delete($conf->{root_pw})) {
my $pipe;
logger('debug', 'Resetting root password');
unless (open($pipe, '|-', '/usr/sbin/chpasswd')) {
my $err = $!;
logger('error', "Could not run chpasswd: $err");
die $err;
}
$pipe->print("root:$pass");
close($pipe);
}
if (my $root_authorized_key = delete($conf->{root_authorized_key})) {
my $fh;
logger('debug', "Adding key to root's authorized_keys");
if(! -d "/root/.ssh") {
if(!mkdir("/root/.ssh", 0700)) {
my $err = sprintf("Could not create /root/.ssh directory: %s", $!);
logger('error', $err);
die $err;
}
}
unless ($fh = IO::File->new('/root/.ssh/authorized_keys', 'w', 0600)) {
my $err = $!;
logger('error', "Could not write /root/.ssh/authorized_keys: $err");
die $err;
}
$fh->print($root_authorized_key);
$fh->close;
}
if (my $name = delete($conf->{hostname})) {
my $fh;
logger('debug', "Setting hostname to '$name'");
unless ($fh = IO::File->new('/etc/hostname', 'w')) {
my $err = $!;
logger('error', "Could not write hostname '$name': $err");
die $err;
}
$fh->print($name);
$fh->close;
system('hostname', '--file', '/etc/hostname');
}
rewrite_conf_file($filename, $conf);
exit 0;
sub read_conf {
my ($file, $conf, $fh);
$file = shift;
$conf = {};
unless ($fh = IO::File->new($filename, 'r')) {
my $err = $!;
logger('error', "Could not read from configuration file '$filename': $err");
# Not finding the config file is not fatal: there is just
# nothing to configure!
return $conf;
}
while (my $line = $fh->getline) {
my ($key, $value);
# Allow for comments, and properly ignore them
$line =~ s/#.+//;
if ( ($key, $value) = ($line =~ m/^\s*([^=]+)\s*=\s*(.*)\s*$/)) {
$key = lc($key);
if (exists($conf->{$key})) {
logger('warn',
"Repeated configuration key: $key. " .
"Overwriting with new value ($value)");
}
$conf->{$key} = $value;
}
}
$fh->close;
return $conf;
}
sub logger {
my ($prio, $msg) = @_;
system('logger', '-p', "daemon.$prio",
'-t', 'rpi-set-sysconf', $msg);
}
sub rewrite_conf_file {
my ($filename, $conf) = @_;
my $fh;
unless ($fh = IO::File->new($filename, 'w')) {
my $err = $!;
logger('error', "Could not write to configuration file '$filename': $err");
die $err;
}
$fh->print(
q(# This file will be automatically evaluated and installed at next boot
# time, and regenerated (to avoid leaking passwords and such information).
#
# To force it to be evaluated immediately, you can run (as root):
#
# /usr/local/sbin/rpi-set-sysconf
#
# You can disable the file evaluation by disabling the rpi-set-sysconf
# service in systemd:
#
# systemctl disable rpi-set-sysconf
#
# Comments (all portions of a line following a '#' character) are
# ignored. This file is read line by line. Valid
# configuration lines are of the form 'key=value'. Whitespace around
# 'key' and 'value' is ignored. This file will be _regenerated_ every
# time it is evaluated.
#
# We follow the convention to indent with one space comments, and
# leave no space to indicate the line is an example that could be
# uncommented.
# root_pw - Set a password for the root user (by default, it allows
# for a passwordless login)
#root_pw=FooBar
# root_authorized_key - Set an authorized key for a root ssh login
#root_authorized_key=
# hostname - Set the system hostname.
#hostname=rpi
));
if (scalar keys %$conf) {
logger('warn', 'Unprocessed keys left in $filename: ' .
join(', ', sort keys %$conf));
$fh->print(
q(
# We found the following unhandled keys - That means, the
# configuration script does not know how to handle them. Please
# double-check them!
));
$fh->print(join('', map {sprintf("%s=%s\n", $_, $conf->{$_})} sort keys %$conf));
}
$fh->close;
}