From 331f68931a0726f2c790aa69bf1d6f23054c6321 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sun, 8 Oct 2017 22:18:48 +0200 Subject: [PATCH] Initial commit --- .gitignore | 8 ++ .gitmodules | 3 + LICENSE | 27 ++++++ README.md | 41 +++++++++ compress.sh | 34 +++++++ eth0 | 6 ++ fstab | 5 ++ raspi3.yaml | 134 ++++++++++++++++++++++++++++ rpi3-generate-ssh-host-keys.service | 10 +++ rpi3-resizerootfs | 125 ++++++++++++++++++++++++++ rpi3-resizerootfs.service | 11 +++ rules.v4 | 13 +++ rules.v6 | 11 +++ vmdb2 | 1 + 14 files changed, 429 insertions(+) create mode 100644 .gitignore create mode 100644 .gitmodules create mode 100644 LICENSE create mode 100644 README.md create mode 100755 compress.sh create mode 100644 eth0 create mode 100644 fstab create mode 100644 raspi3.yaml create mode 100644 rpi3-generate-ssh-host-keys.service create mode 100755 rpi3-resizerootfs create mode 100644 rpi3-resizerootfs.service create mode 100644 rules.v4 create mode 100644 rules.v6 create mode 160000 vmdb2 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