commit 331f68931a0726f2c790aa69bf1d6f23054c6321 Author: Michael Stapelberg Date: Sun Oct 8 22:18:48 2017 +0200 Initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..f20ba5f --- /dev/null +++ b/.gitignore @@ -0,0 +1,8 @@ +# suggested build result filename +raspi3.img +# after compress.sh +compr.img +compr.img.bz2 +# log file names used for the published image +01-vmdb2.log +02-compress.log diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..e5cf326 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "vmdb2"] + path = vmdb2 + url = https://github.com/larswirzenius/vmdb2.git diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..1fb9f59 --- /dev/null +++ b/LICENSE @@ -0,0 +1,27 @@ +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: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + * 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. + + * 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 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. diff --git a/README.md b/README.md new file mode 100644 index 0000000..d04743e --- /dev/null +++ b/README.md @@ -0,0 +1,41 @@ +# Raspberry Pi 3 image spec + +This repository contains the files with which the image referenced at +https://wiki.debian.org/RaspberryPi3 has been built. + +## Option 1: Downloading an image + +See https://wiki.debian.org/RaspberryPi3#Preview_image for where to obtain the latest pre-built image. + +## Option 2: Building your own image + +If you prefer, you can build a Debian buster Raspberry Pi 3 image using: + +```shell +git clone --recursive https://github.com/Debian/raspi3-image-spec +cd raspi3-image-spec +sudo ./vmdb2/vmdb2 --output raspi3.img raspi3.yaml --log raspi3.log +``` + +## Installing the image onto the Raspberry Pi 3 + +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/sdb`, copy the image onto the SD card: + +```shell +sudo dd if=raspi3.img of=/dev/sdb bs=5M +``` + +Then, plug the SD card into the Raspberry Pi 3 and power it up. + +The image uses the hostname `rpi3`, so assuming your local network correctly resolves hostnames communicated via DHCP, you can log into your Raspberry Pi 3 once it booted: + +```shell +ssh root@rpi3 +# Enter password “raspberry” +``` + +## Reproducibility + +The image currently uses http://snapshot.debian.org/archive/debian/20171007T213914Z/ for ensuring a reproducible build. diff --git a/compress.sh b/compress.sh new file mode 100755 index 0000000..eeda0f9 --- /dev/null +++ b/compress.sh @@ -0,0 +1,34 @@ +#!/bin/bash +# Copies raspi3.img into compr.img, resulting in many consecutive zero bytes +# which are nicely compressible. + +set -e + +qemu-img create -f raw compr.img 1000M + +# copy partition table from raspi3.img +sfdisk --quiet --dump raspi3.img | sfdisk --quiet compr.img + +readarray rmappings < <(sudo kpartx -asv raspi3.img) +readarray cmappings < <(sudo kpartx -asv compr.img) + +# copy the vfat boot partition as-is +set -- ${rmappings[0]} +rboot="$3" +set -- ${cmappings[0]} +cboot="$3" +sudo dd if=/dev/mapper/${rboot?} of=/dev/mapper/${cboot?} bs=5M status=none + +# copy the ext4 root partition in a space-saving way +set -- ${rmappings[1]} +rroot="$3" +set -- ${cmappings[1]} +croot="$3" +sudo e2fsck -y -f /dev/mapper/${rroot?} +sudo resize2fs /dev/mapper/${rroot?} 700M +sudo e2image -rap /dev/mapper/${rroot?} /dev/mapper/${croot?} + +sudo kpartx -ds raspi3.img +sudo kpartx -ds compr.img + +bzip2 -k -9 -f compr.img diff --git a/eth0 b/eth0 new file mode 100644 index 0000000..8732488 --- /dev/null +++ b/eth0 @@ -0,0 +1,6 @@ +auto eth0 + +# TODO: switch back to iptables-persistent once it re-enters testing +iface eth0 inet dhcp + pre-up iptables-restore < /etc/iptables/rules.v4 + pre-up ip6tables-restore < /etc/iptables/rules.v6 diff --git a/fstab b/fstab new file mode 100644 index 0000000..5dd5a98 --- /dev/null +++ b/fstab @@ -0,0 +1,5 @@ +# The root file system has fs_passno=1 as per fstab(5) for automatic fsck. +/dev/mmcblk0p2 / ext4 rw 0 1 +# All other file systems have fs_passno=2 as per fstab(5) for automatic fsck. +/dev/mmcblk0p1 /boot/firmware vfat rw 0 2 +proc /proc proc defaults 0 0 diff --git a/raspi3.yaml b/raspi3.yaml new file mode 100644 index 0000000..a5b0a4f --- /dev/null +++ b/raspi3.yaml @@ -0,0 +1,134 @@ +# See https://wiki.debian.org/RaspberryPi3 for known issues and more details. + +steps: + - mkimg: "{{ output }}" + size: 1500M + + - mklabel: msdos + device: "{{ output }}" + + - mkpart: primary + fs-type: 'fat32' + device: "{{ output }}" + start: 0% + end: 20% + part-tag: boot-part + + - mkpart: primary + device: "{{ output }}" + start: 20% + end: 100% + part-tag: root-part + + - mkfs: vfat + partition: boot-part + + - mkfs: ext4 + partition: root-part + + - mount: root-part + fs-tag: root-fs + + - mount: boot-part + mount-on: root-fs + dirname: '/boot/firmware' + fs-tag: boot-fs + + # Without copying the archive keyring into the chroot, debootstraps second + # stage will revert to a known-working HTTPS mirror. As snapshot.d.o does + # not provide HTTPS at this point, we need to avert that. + - shell: | + mkdir -p "${ROOT?}/usr/share/keyrings" + cp /usr/share/keyrings/debian-archive-keyring.gpg "${ROOT?}/usr/share/keyrings/debian-archive-keyring.gpg" + root-fs: root-fs + + # We need to use Debian buster (currently testing) instead of Debian stretch + # (currently stable) for: + # + # linux ≥ 4.13.4-1 + # Which includes the sdhost driver for faster SD card access and making the + # WiFi chip available. + # + # raspi3-firmware ≥ 1.20171006-1 + # Which includes a recent enough firmware version to correctly pass the MAC + # address to the kernel. This is a regression with Linux ≥ 4.12, see + # https://github.com/raspberrypi/firmware/issues/846 + - qemu-debootstrap: buster + mirror: http://snapshot.debian.org/archive/debian/20171007T213914Z + target: root-fs + arch: arm64 + components: + - main + - contrib + - non-free + + # TODO(https://bugs.debian.org/877855): remove this workaround once + # debootstrap is fixed + - chroot: root-fs + shell: | + echo 'deb http://snapshot.debian.org/archive/debian/20171007T213914Z buster main contrib non-free' > /etc/apt/sources.list + apt-get update + + - apt: install + packages: + - ssh + - dosfstools + # Contains /lib/firmware/brcm/brcmfmac43430-sdio.bin (required for WiFi). + - firmware-brcm80211 + - wireless-tools + - wpasupplicant + fs-tag: root-fs + + # TODO: install raspi3-firmware and linux-image-arm64 from buster once they + # migrated in sufficiently recent versions. + - chroot: root-fs + shell: | + echo 'deb http://snapshot.debian.org/archive/debian/20171007T213914Z unstable main contrib non-free' >> /etc/apt/sources.list + echo 'APT::Default-Release "buster";' > /etc/apt/apt.conf.d/08default-release + apt-get update + apt-get -y --no-show-progress -t unstable install raspi3-firmware linux-image-arm64 + + - shell: | + echo "rpi3" > "${ROOT?}/etc/hostname" + + # '..VyaTFxP8kT6' is crypt.crypt('raspberry', '..') + sed -i 's,root:[^:]*,root:..VyaTFxP8kT6,' "${ROOT?}/etc/shadow" + + sed -i 's,#PermitRootLogin prohibit-password,PermitRootLogin yes,g' "${ROOT?}/etc/ssh/sshd_config" + + install -m 644 -o root -g root fstab "${ROOT?}/etc/fstab" + + install -m 644 -o root -g root eth0 "${ROOT?}/etc/network/interfaces.d/eth0" + + mkdir -p "${ROOT?}/etc/iptables" + install -m 644 -o root -g root rules.v4 "${ROOT?}/etc/iptables/rules.v4" + install -m 644 -o root -g root rules.v6 "${ROOT?}/etc/iptables/rules.v6" + + install -m 755 -o root -g root rpi3-resizerootfs "${ROOT?}/usr/bin/rpi3-resizerootfs" + install -m 644 -o root -g root rpi3-resizerootfs.service "${ROOT?}/etc/systemd/system" + mkdir -p "${ROOT?}/etc/systemd/system/systemd-remount-fs.service.requires/" + ln -s /etc/systemd/system/rpi3-resizerootfs.service "${ROOT?}/etc/systemd/system/systemd-remount-fs.service.requires/rpi3-resizerootfs.service" + + install -m 644 -o root -g root rpi3-generate-ssh-host-keys.service "${ROOT?}/etc/systemd/system" + mkdir -p "${ROOT?}/etc/systemd/system/multi-user.target.requires/" + ln -s /etc/systemd/system/rpi3-generate-ssh-host-keys.service "${ROOT?}/etc/systemd/system/multi-user.target.requires/rpi3-generate-ssh-host-keys.service" + rm -f ${ROOT?}/etc/ssh/ssh_host_*_key* + + cat >> "${ROOT?}/etc/motd" <<'EOT' + + Please change the root password by running passwd + EOT + root-fs: root-fs + + # Clean up archive cache (likely not useful) and lists (likely outdated) to + # reduce image size by several hundred megabytes. + - chroot: root-fs + shell: | + apt-get clean + rm -rf /var/lib/apt/lists + + # 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: root-fs diff --git a/rpi3-generate-ssh-host-keys.service b/rpi3-generate-ssh-host-keys.service new file mode 100644 index 0000000..496f2c4 --- /dev/null +++ b/rpi3-generate-ssh-host-keys.service @@ -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 diff --git a/rpi3-resizerootfs b/rpi3-resizerootfs new file mode 100755 index 0000000..5ea6b54 --- /dev/null +++ b/rpi3-resizerootfs @@ -0,0 +1,125 @@ +#!/usr/bin/env perl +# vim:ts=4:sw=4:et +# +# © 2017 Michael Stapelberg +# +# Extends the root partition and root file system to cover the entire remainder +# of the device. Requires the root file system to be a block device matching +# /.+p[0-9]$/, e.g. /dev/mmcblk0p2. +# +# Requires only perl-base (present on all Debian installations) + +use strict; +use Fcntl qw(:DEFAULT :seek); + +################################################################################ +# Find the root device by looking at what is mounted on / +################################################################################ + +sub slurp { + my ($fn) = @_; + open(my $fh, '<', $fn) + or die qq|open($fn): $!|; + local $/; + <$fh>; +} + +my ($rootpart) = grep { $_ ne '' } map { /([^ ]+) \/ / && $1 } split("\n", slurp()); +my $rootdev; +my $pno; +if ($rootpart =~ /^(.+)p([0-9])$/) { + $rootdev = $1; + $pno = int($2); +} else { + die qq|root partition "$rootpart" unexpectedly does not end in p[0-9]|; +} + +################################################################################ +# Get the size of the root block device in bytes +################################################################################ + +sub SYS_ioctl { 29; } # aarch64-specific +sub BLKGETSIZE64 { 2148012658; } + +sysopen(my $root, $rootdev, O_RDWR) + or die qq|sysopen($rootdev): $!|; + +my $devsizep = pack("Q", ()); +$! = 0; +syscall(SYS_ioctl(), fileno($root), BLKGETSIZE64(), $devsizep) >= 0 + or die qq|ioctl(BLKGETSIZE64): $!|; +my ($devsize) = unpack("Q", $devsizep); + +################################################################################ +# Read the partition table entry +################################################################################ + +sub boot_code { 446; } +sub partition_table_entry { 16; } +sub partition_table_start_offset { 8; } +sub partition_table_length_offset { 12; } + +sub read_uint32_le { + my ($offset) = @_; + sysseek($root, $offset, SEEK_SET) + or die qq|sysseek($offset): $!|; + my $buf; + sysread($root, $buf, 4) + or die qq|sysread(): $!|; + my ($val) = unpack("V", $buf); + return $val; +} + +my $entry_offset = boot_code() + (partition_table_entry() * ($pno - 1)); +my $start = 512 * read_uint32_le($entry_offset + partition_table_start_offset()); +my $oldlength = 512 * read_uint32_le($entry_offset + partition_table_length_offset()); +my $newlength = ($devsize - $start); +if ($oldlength == $newlength) { + print "Partition $rootpart already at maximum size $newlength\n"; + exit 0; +} + +################################################################################ +# Change the partition length +################################################################################ + +sub write_uint32_le { + my ($offset, $val) = @_; + sysseek($root, $offset, SEEK_SET) + or die qq|sysseek($offset): $!|; + my $buf = pack("V", $val); + syswrite($root, $buf, 4) + or die qq|syswrite: $!|; +} + +print "Resizing partition $rootpart from $oldlength to $newlength bytes\n"; +write_uint32_le($entry_offset + partition_table_length_offset(), $newlength / 512); + +################################################################################ +# Tell linux about the new partition size using the BLKPG ioctl (BLKRRPART +# cannot be used when the device is mounted, even read-only). +################################################################################ + +sub BLKPG { 0x1269; } +sub BLKPG_RESIZE_PARTITION { 3; } + +my $part = pack("q q i Z64 Z64", $start, $newlength, $pno, "devname", "volname"); +my $partb = "\x00" x length($part); +my $op = BLKPG_RESIZE_PARTITION(); +my $ioctl_arg = pack("i i i x4 p", $op, 0, length($part), $part); +$! = 0; +syscall(SYS_ioctl(), fileno($root), BLKPG(), $ioctl_arg) >= 0 + or die "ioctl(BLKPG): $!"; + +################################################################################ +# Run resize2fs to enlarge the file system +################################################################################ + +# resize2fs requires the file system to be writeable. +my @remountcmd = ('mount', '-o', 'remount,rw', $rootpart); +system(@remountcmd) == 0 + or die qq|system(| . join(" ", @remountcmd) . qq|): $!|; + +my @resizecmd = ('resize2fs', "${rootpart}"); +exec { $resizecmd[0] } @resizecmd + or die qq|exec(| . join(" ", @resizecmd) . qq|): $!|; diff --git a/rpi3-resizerootfs.service b/rpi3-resizerootfs.service new file mode 100644 index 0000000..4a08bc6 --- /dev/null +++ b/rpi3-resizerootfs.service @@ -0,0 +1,11 @@ +[Unit] +Description=resize root file system +Before=systemd-remount-fs.service +DefaultDependencies=no + +[Service] +Type=oneshot +ExecStart=/usr/bin/rpi3-resizerootfs + +[Install] +RequiredBy=systemd-remount-fs.service diff --git a/rules.v4 b/rules.v4 new file mode 100644 index 0000000..1c2d3dd --- /dev/null +++ b/rules.v4 @@ -0,0 +1,13 @@ +# Generated by iptables-save v1.6.0 on Wed Mar 22 14:31:11 2017 +*filter +:INPUT ACCEPT [0:0] +:FORWARD ACCEPT [0:0] +:OUTPUT ACCEPT [0:0] +-A INPUT -s 127.0.0.0/8 -m comment --comment "RFC3330 loopback" -j ACCEPT +-A INPUT -s 10.0.0.0/8 -m comment --comment "RFC1918 reserved" -j ACCEPT +-A INPUT -s 172.16.0.0/12 -m comment --comment "RFC1918 reserved" -j ACCEPT +-A INPUT -s 192.168.0.0/16 -m comment --comment "RFC1918 reserved" -j ACCEPT +-A INPUT -s 169.254.0.0/16 -m comment --comment "RFC3927 link-local" -j ACCEPT +-A INPUT -p tcp -m tcp --dport 22 -m comment --comment SSH -j REJECT --reject-with icmp-port-unreachable +COMMIT +# Completed on Wed Mar 22 14:31:11 2017 diff --git a/rules.v6 b/rules.v6 new file mode 100644 index 0000000..ba23632 --- /dev/null +++ b/rules.v6 @@ -0,0 +1,11 @@ +# Generated by ip6tables-save v1.6.0 on Wed Mar 22 14:31:11 2017 +*filter +:INPUT ACCEPT [0:0] +:FORWARD ACCEPT [0:0] +:OUTPUT ACCEPT [0:0] +-A INPUT -s ::1/128 -m comment --comment "RFC3513 loopback" -j ACCEPT +-A INPUT -s fc00::/7 -m comment --comment "RFC4193 reserved" -j ACCEPT +-A INPUT -s fe80::/10 -m comment --comment "RFC4291 link-local" -j ACCEPT +-A INPUT -p tcp -m tcp --dport 22 -m comment --comment SSH -j REJECT --reject-with icmp6-port-unreachable +COMMIT +# Completed on Wed Mar 22 14:31:11 2017 diff --git a/vmdb2 b/vmdb2 new file mode 160000 index 0000000..6770948 --- /dev/null +++ b/vmdb2 @@ -0,0 +1 @@ +Subproject commit 67709480586340533d23be85987080469f2eebb5