I mentioned in my targeted PXE booting post that I might migrate from maintaining copies of the kernel and initial ramdisks on the tftp server to asking iPXE to fetch them directly. This has been brought to the fore after I updated the mirror in my home lab environment and the installer no longer starts due to not being able to load its kernel modules. When I fetched the image from the mirror and compared its checksum to the one in my configuration management system I saw it had changed.

This is the error I see from the installer showing the problem:

Photo of kernel module error on screen

Remotely fetching the image directly

In the original configuration, Salt fetches the image from the mirror (e.g. http://ftp.uk.debian.org/debian/dists/bullseye/main/installer-amd64/current/images/netboot/debian-installer/amd64/linux) and deploys it to the tftp server. iPXE is then configured to load the image from there. I am reconfiguring iPXE to fetch the image directly, initially from the local mirror in the lab network.

The original configuration lines (in this case for Debian’s bullseye (current stable at the time of writing) release were:

:bullseye-amd64
kernel ../bullseye-amd64/linux initrd=initrd.gz -- quiet
initrd ../bullseye-amd64/initrd.gz
boot

The edited ones (in the lab) :

:bullseye-amd64
kernel http://mirror/mirrors/debian/dists/bullseye/main/installer-amd64/current/images/netboot/debian-installer/amd64/linux initrd=initrd.gz -- quiet
initrd http://mirror/mirrors/debian/dists/bullseye/main/installer-amd64/current/images/netboot/debian-installer/amd64/initrd.gz
boot

This worked (shockingly, on the first attempt) and the installer was able to load correctly.

For CentOS I updated the entries like this - for the live network, fetching from the internet, http://mirror/mirrors/centos-7-os becomes http://www.mirrorservice.org/sites/mirror.centos.org/7/os/x86_64:

:centos-7-x86_64
kernel http://mirror/mirrors/centos-7-os/images/pxeboot/vmlinuz initrd=initrd.img -- ip=dhcp inst.repo=http://mirror/mirrors/centos-7-os/ rhgb quiet
initrd http://mirror/mirrors/centos-7-os/images/pxeboot/initrd.img
boot

Including images in mirrors

See my previous post on improving my mirror synchronisation script for the original version of these scripts that these changes apply to.

Debian

I initially had not included the installers in my Debian mirrors, fortunately adding them is a case of enabling the option di-dist or di-arch when calling debmirror however this caused debmirror to fail if the mirror does not have installers (e.g. updates and security mirrors). To work round this, I added an option to the role and made it an option when using the role.

In defaults/main.yml I set the default value to be false (maintains old behaviour by default and more mirrors do not have installers to fetch than do):

installers: false

and added the option in meta/argument_specs.yml:

      installers:
        type: bool
        default: false
        description: Fetch the Debian installers (that match the architectures and suites requested)

Finally, modifying the Run debmirror task in tasks/main.yml to apply the option:

      # Debian installers as well, if required. Bit of a hack - repeat
      # the --verbose if not wanted to put _something_ in the argument
      # list.
      - "--verbose"
      - "--verbose"

In the the debian-mirrors role, which uses the debmirror role to mirror the specific mirrors I need, I set the new installers option to true on just the main mirror in meta/main.yml:

---
dependencies:
  - role: debmirror
    source:
      host: rsync.mirrorservice.org
      root: ftp.debian.org/debian
    target: '/debian'
    installers: true
    keyring_directory: '/keyrings/debian'
    suites:
      - buster
      - buster-updates
      - bullseye
      - bullseye-updates
    keys:
      - name: debian-10-buster
        url: https://ftp-master.debian.org/keys/archive-key-10.asc
        fingerprint: "80D1 5823 B7FD 1561 F9F7 BCDD DC30 D7C2 3CBB ABEE"
      - name: debian-11-buster
        url: https://ftp-master.debian.org/keys/archive-key-11.asc
        fingerprint: "1F89 983E 0081 FDE0 18F3 CC96 73A4 F27B 8DD4 7936"
...

Yum repositories

This applies to CentOS and Rocky. Unlike debmirror, reposync has no option to download images automatically or specifically download extra files so they have to fetched separately. This does have the advantage that rather than selectively download via a variable (like I did with Debian) I test for the existence of the images folder and download them based on that.

In the reposync role’s tasks/main.yml, I included an image fetch task for each repository. I pulled out the files as a variable, although they are the same for CentOS 7 and Rocky 8 I fear they might change in the future:

- name: Fetch boot/installer images for each repository
  include_tasks: fetch_additional_files.yaml
  vars:
    source: ""
    destination: "/"
    extra_files:
      - images/pxeboot/vmlinuz
      - images/pxeboot/initrd.img
      - LiveOS/squashfs.img # CentOS 7 installer
      - images/install.img # Rocky 8 installer
  loop: ''
  loop_control:
    loop_var: repo # Avoid conflict with inner loop(s)

The fetch_additional_files.yaml file contains:

---
- name: Test if source exists (might not for, e.g., updates mirrors)
  ansible.builtin.uri:
    url: "/"
    method: HEAD
    status_code:
      - 200
      - 404  # Not found is an expected potential outcome
  register: test_file_uris
  loop: ''
- name: Ensure target directories exists
  ansible.builtin.file:
    name: '/'
    state: directory
  loop: ''
  when: item.status == 200
- name: Fetch files
  ansible.builtin.get_url:
    dest: "//"  # Ensure ends in `/` to indicate directory
    url: ''
  loop: ''
  when: item.status == 200
...

DBAN

DBAN was a little more interesting, because getting the image to PXE boot requires extracting it from the DBAN ISO image. In my old configuration, I pushed a manually extracted copy of the image stored in the configuration management code repository. I want to get away from this, it violates the principal of duplicating something that can get obtained from a more authoritative source and it puts a large (16MB) binary file into source control which is bad practice.

So, I added DBAN to my mirror with a new role. The role takes two arguments, target (where to mirror to) and version (which version of DBAN to fetch). First thing was to create meta/argument_specs.yaml:

---
argument_specs:
  main:
    short_description: Mirror DBAN ISO and extract dban image (e.g. for PXE booting)
    options:
      target:
        type: str
        required: true
        description: Directory to mirror to
      version:
        type: str
        required: true
        description: Version of DBAN to fetch
...

Then the actual task in tasks/main.yaml:

---
- name: Make sure isoinfo is installed
  become: yes
  ansible.builtin.package:
    name: genisoimage
    state: present
- name: Make target directory
  ansible.builtin.file:
    path: ""
    state: directory
- name: Download iso
  ansible.builtin.get_url:
    dest: '/'
    url: https://sourceforge.net/projects/dban/files/dban/dban-/dban-_i586.iso/download
- name: Extract DBan image
  ansible.builtin.shell:
    cmd: >
      isoinfo
      -i /dban-_i586.iso
      -x '/DBAN.BZI;1'
      > /dban.bzi
- name: State the extracted file (check for silent failure of isoinfo)
  ansible.builtin.stat:
    path: '/dban.bzi'
  register: dban_extracted_image_stat
- name: Check size
  ansible.builtin.assert:
    fail_msg: Extracted image does not exist or is zero sized
    that:
      - dban_extracted_image_stat.stat.exists
      - dban_extracted_image_stat.stat.size > 0
...

To add DBAN to the mirror, I just added the role to the roles list in site.yaml:

    - role: dban
      target: "/dban-2.3.0"
      version: 2.3.0
      tags: ['dban']

It is worth noting that DBAN only works on BIOS booted systems, not EFI.

Final change

After committing, I also renamed all the .yml files to .yaml and committed that too - a convention I moved to a while a go and brings this code in line with my others.