Netboot

Updated: September 28, 2024

Settings up a cluster of raspberry pi for netbooting.

Primariily will setup nfs for storage and tftp for the boot files. This can be accomplished
with trueNAS or Synology. As long as it has large volume and is protected from corruption.


Table of Contents

Synology

From: Network > Network Interface > Create

From: File Services > NFS > Enable; set protocol to NFSv4

From: Shared Folder > Create rootmnt and bootmnt

NFS Permissions

From: File Services > Advanced > TFTP

Install DHCP Server from Package Center

Pi4

Offering two ways to setup pi. Using ansible or SSH and run commands line by line.

Ansible

Keep in mind to change things like hostname and ip under vars:

---
- name: Preparing pi4 for Netbooting.
  hosts: all
  become: yes

  vars:
    hostname: euryale
    synology_ip: 192.168.1.87

  tasks:
  - name: Setting new hostname...
    hostname:
        name: "{{ hostname }}"

  - name: Updating /etc/hosts file with new hostname...
    lineinfile:
        dest: /etc/hosts
        regexp: '127.0.0.1.*'
        line: '127.0.0.1 "{{ hostname }}"'
        state: present

  - name: Updating /etc/hostname file with new hostname...
    lineinfile:
        dest: /etc/hostname
        line: '"{{ hostname }}"'
        state: present
        
  - name: Rebooting system to apply changes...
    reboot:
        reboot_timeout:300
    when: ansible_os_family == 'Debian'

  - name: Updating package index...
    apt:
        update_cache: yes
  
  - name: Installing apts...
    apt:
        name: "{{ packages }}"
        state: present
    vars:
        packages:
        - nfs-common
        - rpi-eeprom
        
  - name: Creating root mount directory on the pi...
    file:
        path: /nfs/"{{ hostname }}"
        state: directory
        owner: root
        group: root
        mode: 0755

  - name: Mounting root NFS share...
    mount:
        path: /nfs/"{{ hostname }}"
        src: "{{ synology_ip }}":/volume1/rootmnt/"{{ hostname }}"
        fstype: nfs
        opts: rw,proto=tcp,port=2049,all_squash,anonuid=1001,anongid=1001

  - name: Copying OS files to synology...
    synchronize:
        src: /
        dest: /nfs/"{{ hostname }}"/
        archive: yes
        compress: yes
        delete: yes
        progress: yes
        rsync_opts:
            - "--exclude /nfs"

  - name: Creating boot mount directory on the pi...
    file:
        path: /nfs/bootmnt
        state: directory
        owner: root
        group: root
        mode: 0755

  - name: Mounting boot NFS share...
    mount:
        path: /nfs/bootmnt
        src: "{{ synology_ip }}":/volume1/bootmnt
        fstype: nfs
        opts: rw,proto=tcp,port=2049,all_squash,anonuid=1001,anongid=1001

  - name: Grabbing pi4's serial number...
    shell: vcgencmd otp_dump | grep 28: | sed s/.*://g
    register: otp_value

  - name: Setting serial number into fact...
    set_fact:
        otp: "{{ otp_value.stdout }}"

  - name: Creating directory based on serial number...
    file:
        path: /nfs/bootmnt/"{{ otp }}"
        state: directory
        owner: root
        group: root
        mode: 0755

  - name: Copying boot files over to synology...
    copy:
        src: /boot/
        dest: /nfs/bootmnt/"{{ otp }}"
        owner: root
        group: root
        mode: 0755
        recurse: yes

  - name: creating backup of fstab file...
    copy:
      src: /etc/fstab
      dest: /etc/fstab.bak

  - name: updating fstab file...
    lineinfile:
      path: /etc/fstab
      line: |
        proc            /proc           proc    defaults          0       0
        "{{ synology_ip }}":/volume1/bootmnt/"{{ otp }}" /boot nfs defaults,vers=3,proto=tcp,bind 0 0        
      state: present

  - name: creating back of cmdline.txt file...
    copy:
      src: /nfs/bootmnt/"{{ otp }}"/cmdline.txt
      dest: /nfs/bootmnt/"{{ otp }}"/cmdline.txt.bak

  - name: updating cmdline.txt file...
    lineinfile:
      path: /nfs/bootmnt/"{{ otp }}"/cmdline.txt
      line: |
                console=serial0,115200 console=tty1 root=/dev/nfs nfsroot="{{ synology_ip }}:/volume1/rootmnt/"{{ hostname }}",vers=3 rw ip=dhcp elevator=deadline rootwait

  - name: create ssh file so we can do dat...
    file:
      path: /nfs/bootmnt/"{{ otp }}"/boot/ssh
      state: touch
      owner: root
      group: root
      mode: 0600

  - name: Removing pieeprom-new.bin before making new one...
      file:
        path: ~/tmp/pieeprom-new.bin
        state: absent
  
  - name: Copy firmware file
      copy:
        src: /lib/firmware/raspberrypi/bootloader/stable/pieeprom-2022-03-10.bin
        dest: ~/tmp/pieeprom.bin
  
  - name: Creating boot configuration file...
      lineinfile:
        path: /nfs/bootmnt/"{{ otp }}"/bootconf.txt
        line: |
          [all]
          BOOT_UART=0
          WAKE_ON_GPIO=1
          POWER_OFF_ON_HALT=0
          DHCP_TIMEOUT=45000
          DHCP_REQ_TIMEOUT=4000
          TFTP_FILE_TIMEOUT=30000
          TFTP_IP=192.168.1.87
          TFTP_PREFIX=0
          ENABLE_SELF_UPDATE=1
          DISABLE_HDMI=0
          BOOT_ORDER=0x241
          SD_CARD_MAX_RETRIES=3
          NET_BOOT_MAX_RETRIES=5          

    - name: Generating new eeprom binary...
      shell: rpi-eeprom-config --out pieeprom-new.bin --config bootconf.txt pieeprom.bin

    - name: Writing new eeprom binary....
      shell: rpi-eeprom-update -d -f ./pieeprom-new.bin

    - name: Rebooting the Pi4 (leave sd card in for the last time)
      reboot:
        reboot_timeout: 300

After the reboot check: