Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
102 changes: 102 additions & 0 deletions documentation/asciidoc/computers/remote-access/old/prepare_pxetools.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
#!/bin/bash

set -e

sudo apt install -y 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 | head -n 1 | 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)

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 stop dhcpcd
sudo systemctl disable dhcpcd
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 install -y nfs-kernel-server dnsmasq iptables-persistent unzip nmap kpartx rsync

sudo mkdir -p /nfs
sudo mkdir -p /tftpboot
sudo cp -r /boot /tftpboot/base
sudo cp /boot/bootcode.bin /tftpboot
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 rpcbind
sudo systemctl restart rpcbind
sudo systemctl enable nfs-kernel-server
sudo systemctl restart nfs-kernel-server

echo "Getting latest Raspberry Pi OS lite image to use as NFS root"
# Get latest Raspberry Pi OS lite image
sudo mkdir -p /nfs/bases
cd /nfs/bases
sudo wget -O raspios_latest.zip https://downloads.raspberrypi.org/raspios_lite_armhf_latest
sudo unzip raspios_latest.zip
sudo rm raspios_latest.zip

sudo wget -O /usr/local/sbin/pxetools https://datasheets.raspberrypi.org/soft/pxetools.py
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this now refer to wherever pxetools.py will end up on the documentation site?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was assuming that I'd be able to point the redirect on datasheets to this new location? (when the time comes 😉 )

sudo chmod +x /usr/local/sbin/pxetools

echo "Now run sudo pxetools --add \$serial"
234 changes: 234 additions & 0 deletions documentation/asciidoc/computers/remote-access/old/pxetools.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,234 @@
#!/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 = None
NFS_IP = None

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]

# Validate serial
serial = serial.lstrip("0")
if not re.search("^[0-9a-f]{8}$", serial):
raise Exception("Invalid serial number {}".format(serial))

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: ")

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 os.geteuid() != 0:
exit("Please run with sudo or as root user")

LAN = cmd('ip -4 addr show dev eth0 | grep inet | cut -d " " -f6 | xargs ipcalc | grep Network | cut -d " " -f4', print_cmd=False).rstrip()
NFS_IP = cmd('ifconfig eth0 | grep "inet addr" | cut -d " " -f 12 | cut -d ":" -f 2', print_cmd=False).rstrip()

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)