diff --git a/hardware/raspberrypi/bootmodes/bootcode.bin b/hardware/raspberrypi/bootmodes/bootcode.bin deleted file mode 100644 index 34a82f41e..000000000 Binary files a/hardware/raspberrypi/bootmodes/bootcode.bin and /dev/null differ diff --git a/hardware/raspberrypi/bootmodes/msd.md b/hardware/raspberrypi/bootmodes/msd.md index f64fea2a4..52341968f 100644 --- a/hardware/raspberrypi/bootmodes/msd.md +++ b/hardware/raspberrypi/bootmodes/msd.md @@ -2,17 +2,15 @@ This tutorial explains how to boot your Raspberry Pi 3 from a USB mass storage device such as a flash drive or USB hard disk. Be warned that this feature is experimental and may not work with all USB mass storage devices. ## Program USB Boot Mode -Before a Pi will network boot, it needs to be booted with a config option to enable USB boot mode. Enabling this config option requires a special `start.elf` and `bootcode.bin` file. +Before a Pi will network boot, it needs to be booted with a config option to enable USB boot mode. Enabling this config option requires a special `start.elf` and `bootcode.bin` file. These can be installed by using the "next" branch on rpi-update. Go to the [Downloads page](https://www.raspberrypi.org/downloads/raspbian/) and install Raspbian onto an SD card using `Win32DiskImager` if you are on Windows, or `dd` if you are on Linux/Mac. Boot the Pi. -First, prepare the `/boot` directory with new `start.elf` and `bootcode.bin` files: +First, prepare the `/boot` directory with experimental boot files: ``` -cd /boot -sudo rm start.elf bootcode.bin start_* fixup* -sudo wget https://github.com/raspberrypi/documentation/raw/master/hardware/raspberrypi/bootmodes/start.elf -sudo wget https://github.com/raspberrypi/documentation/raw/master/hardware/raspberrypi/bootmodes/bootcode.bin -sudo sync +# If on raspbian lite you need to install rpi-update before you can use it: +$ sudo apt-get update; sudo apt-get install rpi-update +$ sudo BRANCH=next rpi-update ``` Then enable USB boot mode with: @@ -37,7 +35,6 @@ Now that your Pi 3 is USB boot-enabled, we can prepare a USB storage device to b We will start by using parted to create a 100MB fat32 partition, followed by a Linux ext4 partition that will take up the rest of the disk. ``` -sudo umount /dev/sda sudo parted /dev/sda (parted) mktable msdos @@ -70,9 +67,25 @@ sudo mkdir /mnt/target sudo mount /dev/sda2 /mnt/target/ sudo mkdir /mnt/target/boot sudo mount /dev/sda1 /mnt/target/boot/ +sudo apt-get update; sudo apt-get install rsync sudo rsync -ax --progress / /boot /mnt/target ``` +Regenerate ssh host keys: +``` +cd /mnt/target +sudo mount --bind /dev dev +sudo mount --bind /sys sys +sudo mount --bind /proc proc +sudo chroot /mnt/target +rm /etc/ssh/ssh_host* +dpkg-reconfigure openssh-server +exit +sudo umount dev +sudo umount sys +sudo umount proc +``` + Edit `/boot/cmdline.txt` so that it uses the USB storage device as the root filesystem instead of the SD card. ``` @@ -86,6 +99,7 @@ sudo sed -i "s,/dev/mmcblk0p,/dev/sda," /mnt/target/etc/fstab Finally, unmount the target filesystems, and power off the Pi. ``` +cd ~ sudo umount /mnt/target/boot sudo umount /mnt/target sudo poweroff diff --git a/hardware/raspberrypi/bootmodes/net_tutorial.md b/hardware/raspberrypi/bootmodes/net_tutorial.md index 215327385..daac49ad9 100644 --- a/hardware/raspberrypi/bootmodes/net_tutorial.md +++ b/hardware/raspberrypi/bootmodes/net_tutorial.md @@ -2,18 +2,16 @@ This tutorial is written to explain how to set up a simple DHCP / TFTP server which will allow you to boot a Raspberry Pi 3 from the network. The tutorial assumes you have an existing home network, and want to use a Raspberry Pi for the **server**. You will need a second Pi 3 as a **client** to be booted. Only one SD card is needed because the **client** will be booted from the **server** after the initial client configuration. ## Client configuration -Before a Pi will network boot, it needs to be booted with a config option to enable USB boot mode. Enabling this config option requires a special `start.elf` and `bootcode.bin` file. +Before a Pi will network boot, it needs to be booted with a config option to enable USB boot mode. Enabling this config option requires a special `start.elf` and `bootcode.bin` file. These can be installed by using the "next" branch on rpi-update. Install Raspbian lite (or heavy if you want) from the [Downloads page](https://www.raspberrypi.org/downloads/raspbian/) onto an SD card using `Win32DiskImager` if you are on Windows, or `dd` if you are on Linux/Mac. Boot the **client** Pi. ### Program USB Boot Mode -First, prepare the `/boot` directory with new `start.elf` and `bootcode.bin` files: +First, prepare the `/boot` directory with experimental boot files: ``` -cd /boot -sudo rm start.elf bootcode.bin start_* fixup* -sudo wget https://github.com/raspberrypi/documentation/raw/master/hardware/raspberrypi/bootmodes/start.elf -sudo wget https://github.com/raspberrypi/documentation/raw/master/hardware/raspberrypi/bootmodes/bootcode.bin -sudo sync +# If on raspbian lite you need to install rpi-update before you can use it: +$ sudo apt-get update; sudo apt-get install rpi-update +$ sudo BRANCH=next rpi-update ``` Then enable USB boot mode with: @@ -45,10 +43,17 @@ sudo rsync -xa --progress --exclude /nfs / /nfs/client1 Regenerate ssh host keys on client filesystem by chrooting into it: ``` -sudo chroot /nfs/client1 +cd /nfs/client1 +sudo mount --bind /dev dev +sudo mount --bind /sys sys +sudo mount --bind /proc proc +sudo chroot . rm /etc/ssh/ssh_host_* dpkg-reconfigure openssh-server exit +sudo umount dev +sudo umount sys +sudo umount proc ``` You need to find the settings of your local network. You need to find the address of your router (or gateway), which you can find with: diff --git a/hardware/raspberrypi/bootmodes/netboot_server_easy.md b/hardware/raspberrypi/bootmodes/netboot_server_easy.md new file mode 100644 index 000000000..fc92d5bf4 --- /dev/null +++ b/hardware/raspberrypi/bootmodes/netboot_server_easy.md @@ -0,0 +1,14 @@ +## pxetools +We have created a Python script that is used internally to quickly set up Pi's that will network boot. It takes a serial number, which you can find in `cat /proc/cpuinfo`, an owner name and the name of the Pi. It then creates a root filesystem for that Pi from a raspbian image. There is also a --list option which will print out the IP address of the Pi, and remove an option. The follwing instructions describe how to set up the environment required by the script starting from a fresh Raspbian lite image. It might be a good idea to mount a hard disk or flash drive on /nfs so that your SD card isn't providing filesystems to multiple Pi's. This is left as an exercise for the reader. + +``` +sudo raspi-config +# Pick expand filesystem option +# Finish +# Reboot + +sudo wget https://raw.githubusercontent.com/raspberrypi/documentation/master/hardware/raspberrypi/bootmodes/pxetools/prepare_pxetools +bash prepare_pxetools + +prepare_pxetools should prepare everything you need to use pxetools +``` diff --git a/hardware/raspberrypi/bootmodes/pxetools/prepare_pxetools b/hardware/raspberrypi/bootmodes/pxetools/prepare_pxetools new file mode 100755 index 000000000..32546e7a7 --- /dev/null +++ b/hardware/raspberrypi/bootmodes/pxetools/prepare_pxetools @@ -0,0 +1,107 @@ +#!/bin/bash + +set -e + +sudo apt-get update; sudo apt-get -y upgrade +sudo apt-get install -y rpi-update +sudo BRANCH=next rpi-update + +sudo apt-get install python3 python3-pip ipcalc +sudo pip3 install tabulate + +# Get network info +NAMESERVER=$(cat /etc/resolv.conf | grep nameserver | head -n 1 | cut -d " " -f2) +GATEWAY=$(ip -4 route | grep default | cut -d " " -f3) +IP=$(ifconfig eth0 | grep "inet addr" | cut -d " " -f 12 | cut -d ":" -f 2) +BRD=$(ifconfig eth0 | grep "inet addr" | cut -d " " -f 14 | cut -d ":" -f 2) +NETMASK=$(ifconfig eth0 | grep "inet addr" | cut -d " " -f 16 | cut -d ":" -f 2) +NETWORK=$(ip -4 addr show dev eth0 | grep inet | cut -d " " -f6 | xargs ipcalc | grep Network | cut -d " " -f4) + +echo "IP: $IP" +echo "Netmask: $NETMASK" +echo "Broadcast: $BRD" +echo "Nameserver: $NAMESERVER" +echo "Gateway: $GATEWAY" + +echo "Setting static IP using above information" + +cat << EOF | sudo tee /etc/network/interfaces +auto lo +iface lo inet loopback + +auto eth0 +iface eth0 inet static + address $IP + netmask $NETMASK + gateway $GATEWAY +EOF + +sudo systemctl restart networking + +# In case it is already set +sudo chattr -i /etc/resolv.conf + +echo "Setting nameserver" +cat << EOF | sudo tee /etc/resolv.conf +nameserver $NAMESERVER +EOF + +# Prevent DNSMasq from changing +sudo chattr +i /etc/resolv.conf + +sudo apt-get install -y nfs-kernel-server dnsmasq iptables-persistent unzip nmap kpartx + +sudo mkdir -p /nfs +sudo mkdir -p /tftpboot +sudo cp -r /boot /tftpboot/base +sudo chmod -R 777 /tftpboot + +echo "Writing dnsmasq.conf" +cat << EOF | sudo tee /etc/dnsmasq.conf +port=0 +dhcp-range=$BRD,proxy +bind-interfaces +log-dhcp +enable-tftp +log-facility=/var/log/dnsmasq +tftp-root=/tftpboot +pxe-service=0,"Raspberry Pi Boot" +EOF + +# Flush any rules that might exist +sudo iptables -t raw --flush + +# Create the DHCP_clients chain in the 'raw' table +sudo iptables -t raw -N DHCP_clients || true + +# Incoming DHCP, pass to chain processing DHCP +sudo iptables -t raw -A PREROUTING -p udp --dport 67 -j DHCP_clients + +# Deny clients not in chain not listed above +sudo iptables -t raw -A DHCP_clients -j DROP + +sudo iptables-save | sudo tee /etc/iptables/rules.v4 + +# Start services +sudo systemctl enable dnsmasq +sudo systemctl restart dnsmasq +sudo systemctl enable nfs-kernel-server +sudo systemctl restart nfs-kernel-server +sudo systemctl enable rpcbind +sudo systemctl restart rpcbind + +echo "Getting latest Raspbian lite image to use as NFS root" +# Get latest Raspbian lite image +sudo mkdir -p /nfs/bases +cd /nfs/bases +sudo wget -O raspbian_latest.zip https://downloads.raspberrypi.org/raspbian_lite_latest +sudo unzip raspbian_latest.zip +sudo rm raspbian_latest.zip + +sudo wget -O /usr/local/sbin/pxetools https://raw.githubusercontent.com/raspberrypi/documentation/master/hardware/raspberrypi/bootmodes/pxetools/pxetools +sudo chmod +x /usr/local/sbin/pxetools + +sudo sed -i "s,LAN = \"10.3.14.0/24\",LAN = \"$NETWORK\"," /usr/local/sbin/pxetools +sudo sed -i "s,NFS_IP = \"10.3.14.18\",NFS_IP = \"$IP\"," /usr/local/sbin/pxetools + +echo "Now run sudo pxetools --add \$serial" diff --git a/hardware/raspberrypi/bootmodes/pxetools/pxetools b/hardware/raspberrypi/bootmodes/pxetools/pxetools new file mode 100755 index 000000000..e6d604cb7 --- /dev/null +++ b/hardware/raspberrypi/bootmodes/pxetools/pxetools @@ -0,0 +1,229 @@ +#!/usr/bin/env python3 + +import os +import re +import subprocess +import time +import sys +from tabulate import tabulate + +HELP = "Usage:\n\ +\tpxetools --add serial\n\ +\tpxetools --remove serial\n\ +\tpxetools --list\n" + +LAN = "10.3.14.0/24" +NFS_IP = "10.3.14.18" + +def cmd(cmd, print_out=False, print_cmd=True, can_fail=False): + if print_cmd: + print("# {}".format(cmd)) + + out = None + + try: + out = subprocess.check_output(cmd, shell=True).decode() + except: + if not can_fail: + raise + + if print_out: + print(out) + + return out + + +def add(): + serial = None + if len(sys.argv) != 3: + if os.path.exists("/usr/local/sbin/get_serial"): + # Auto detect + print("Will try and auto detect serial. Please plug in your Pi now!") + cmd("systemctl stop dnsmasq") + cmd("iptables -t raw --flush") + + try: + serial = cmd("get_serial", print_cmd=False).rstrip() + except KeyboardInterrupt: + cmd("iptables-restore < /etc/iptables/rules.v4") + cmd("systemctl start dnsmasq") + sys.exit() + + print("Found serial") + cmd("iptables-restore < /etc/iptables/rules.v4") + cmd("systemctl start dnsmasq") + else: + serial = sys.argv[2] + + print("Serial: {}".format(serial)) + owner = input("Owner Name (ex Gordon): ") + name = input("Name for pi: ") + + print("Select a base image:") + selection = ["I will prepare my own filesystem"] + os.listdir('/nfs/bases') + for i in range (0, len(selection)): + print("\t{}. {}".format(i + 1, selection[i])) + + img_choice = input("Enter an option number: ") + + # Validate stuff + serial = serial.lstrip("0") + if not re.search("^[0-9a-f]{8}$", serial): + raise Exception("Invalid serial number {}".format(serial)) + + tftp_path = "/tftpboot/{}".format(serial) + nfs_path = "/nfs/{}".format(serial) + if os.path.exists(tftp_path): + raise Exception("{} already exists!".format(tftp_path)) + + valid_img_choice = True + try: + img_choice = int(img_choice) + # Make 0 based again + img_choice -= 1 + except ValueError: + valid_img_choice = False + + if img_choice < 0 or img_choice > (len(selection) - 1) or (not valid_img_choice): + raise Exception("Invalid image choice {}".format(img_choice)) + + img = None + if img_choice > 0: + img = "/nfs/bases/{}".format(selection[img_choice]) + + print("\nSetting up pi...\n") + + cmd("iptables -t raw -I DHCP_clients -m mac --mac-source {0} -j ACCEPT".format(mac(serial))) + cmd("iptables-save > /etc/iptables/rules.v4") + cmd("mkdir {}".format(tftp_path)) + cmd("cp -r /tftpboot/base/* /{}".format(tftp_path)) + cmd("mkdir {}".format(nfs_path)) + cmd("echo \"{} *(rw,sync,no_subtree_check,no_root_squash)\" >> /etc/exports".format(nfs_path)) + cmd("exportfs -a") + + cmdline_txt = "dwc_otg.lpm_enable=0 root=/dev/nfs nfsroot={}:{} rw ip=dhcp rootwait elevator=deadline".format(NFS_IP, nfs_path) + cmd("echo \"{}\" > {}/cmdline.txt".format(cmdline_txt, tftp_path)) + cmd("echo \"{}\" > {}/owner".format(owner, tftp_path)) + cmd("echo \"{}\" > {}/name".format(name, tftp_path)) + + if img_choice: + cmd("mkdir -p /mnt/tmp") + cmd("kpartx -a -v {}".format(img), print_out=True) + time.sleep(1) + cmd("mount /dev/mapper/loop0p2 /mnt/tmp") + cmd("rsync -a /mnt/tmp/ {}/".format(nfs_path)) + cmd("umount /mnt/tmp") + cmd("echo > {}/etc/fstab".format(nfs_path)) + cmd("cd {}/etc/init.d; rm dhcpcd dphys-swapfile raspi-config resize2fs_once".format(nfs_path)) + cmd("cd {}/etc/systemd/system; rm -r dhcp* multi-user.target.wants/dhcp*".format(nfs_path)) + cmd("mount /dev/mapper/loop0p1 /mnt/tmp") + cmd("rsync -a --exclude bootcode.bin --exclude start.elf --exclude cmdline.txt /mnt/tmp/ {}/".format(tftp_path)) + cmd("umount /mnt/tmp") + cmd("kpartx -d -v {}".format(img), print_out=True) + else: + print("You opted to prep your own system so please put files in:\n\t{}\n\t{}".format(tftp_path, nfs_path)) + print("I have wrote you a cmdline.txt so don't change it:") + print(cmdline_txt) + + print("Should be working. You might have to wait a couple of minutes before SSH lets you in") + +def remove(): + serial = sys.argv[2] + + if not re.search("^[0-9a-f]{8}$", serial): + raise Exception("Invalid serial number {}".format(serial)) + + sure = "" + while sure != "Y" and sure != "N": + sure = input("ARE YOU SURE YOU WANT TO DELETE {}? Y/N: ".format(serial)) + + if sure == "N": + print("Aborting") + return + + cmd("rm -rf /tftpboot/{}".format(serial), can_fail=True) + + nfspath = "/nfs/{}".format(serial) + cmd("rm -rf {}".format(nfspath), can_fail=True) + + print("Removing from /etc/exports") + with open("/etc/exports", 'r+') as f: + export = f.read() + f.seek(0) + + for l in export.splitlines(): + if l.startswith(nfspath) == False: + f.write(l + "\n") + + f.truncate() + + mac_uppercase = mac(serial).upper() + print("Removing from iptables rules") + with open("/etc/iptables/rules.v4", 'r+') as f: + rules = f.read() + f.seek(0) + + for l in rules.splitlines(): + if mac_uppercase not in l: + f.write(l + "\n") + + f.truncate() + + cmd("iptables-restore < /etc/iptables/rules.v4") + +def mac(serial): + # MAC is least significant bits of serial + # Example: + # b8:27:eb:be:e3:d2 + # 0000000044bee3d2 + prefix = "b8:27:eb:" + suffix = serial[-6:] + + # Insert colon every 2 chars. Stolen from stackoverflow + s = suffix + s = ":".join(a+b for a,b in zip(s[::2], s[1::2])) + return prefix + s + +def ip(mac): + # Hacky way of getting IP from Mac Address + cmdstr = ("nmap -sn -PR --min-rate 5000 --max-retries=0 {0} " + "| grep -i -B 2 {1} | head -n 1 |" + " awk '{{print $5}}'").format(LAN, mac) + ip = cmd(cmdstr, print_cmd=False) + if len(ip) == 0: + ip = "Not found" + else: + ip = ip.rstrip() + + return ip + +def file_or_none(base, file): + path = os.path.join(base, file) + if not os.path.exists(path): + return "Not Found" + + with open(path, 'r') as fh: + return fh.read() + +def list(): + tbl = [["Serial", "Owner", "Name", "MAC", "IP"]] + pis = os.listdir("/tftpboot") + pis = [p for p in pis if p != "base" and p != "bootcode.bin"] + + for p in pis: + base = os.path.join("/tftpboot", p) + tbl.append(["0x{}".format(p), file_or_none(base, "owner"), file_or_none(base, "name"), mac(p), ip(mac(p))]) + + print(tabulate(tbl, headers="firstrow")) + +if __name__ == "__main__": + if len(sys.argv) == 1: + print(HELP) + elif sys.argv[1] == "--list": + list() + elif sys.argv[1] == "--add": + add() + elif sys.argv[1] == "--remove": + remove() + else: + print(HELP) diff --git a/hardware/raspberrypi/bootmodes/start.elf b/hardware/raspberrypi/bootmodes/start.elf deleted file mode 100644 index 8e4b678d5..000000000 Binary files a/hardware/raspberrypi/bootmodes/start.elf and /dev/null differ