Home custom_rpi_image
Post
Cancel

custom_rpi_image

Building a custom PI image Ubuntu image

Required:

  • A small SD card (The larger the drive the longer the copy will take)
  • A larger external drive
  1. Flash an SD card with the desired operating system as normal with the Rpi imager
  2. Boot a testing pi with the SD and perform the base configurations
  3. Plug the USB and external drive into a linux machine Identify the drives with the following command:
    1
    
    sudo fdisk -l
    
  4. Once you have identified your drives, mount the larger drive to the computer.
  5. Once mounted, run the following to create the ISO file on the external drive
    1
    
    sudo dd bs=4M if=/dev/mmcblk0 of='/media/ah34/New Volume/piubuntu2201.img' status=progress
    
  6. Shrink the ISO with the pishrink
    1
    2
    3
    
    # sudo pishrink -d <image to shrink> <shrunk image> 
    # Example
    sudo ./tools/PiShrink/pishrink source destination
    
  7. The new image can be written to the pi with the Rpi imager

Using a custom image for Raspberry Pi’s

After flashing a pi, mount the “writable” partition of the SD card and run the following and make changes to any files that you want to modify for configurations.

image_customizer.py
#!/usr/bin/python3
import argparse
import os
import requests
import ipaddress
import re
import sys

if __name__ == "__main__":
    os.chdir(os.path.dirname(sys.argv[0]))
    parser = argparse.ArgumentParser()
    parser.add_argument('-i','--ip_address', help='The IP address you want to set for the machine', required=True)
    parser.add_argument('-s','--subnet', help='The IP address you want to set for the machine', required=True)
    parser.add_argument('-d','--dns_server', help='The IP address you want to set for the machine', required=True)
    parser.add_argument('-g','--gateway', help='The IP address you want to set for the machine', required=True)
    parser.add_argument('-n','--hostname', help='The hostname you want to set for the machine', required=True)
    parser.add_argument('-m','--mntpoint', help='The mount point of your SD card. Do not add a trailing / to the path', required=True)
    parser.add_argument('-u', '--username', help='The user name of the user you wish to add SSH keys to', required=True)
    parser.add_argument('-o', '--operating_system', help='Distribution for template files', choices=['ubuntu'], required=True)
    arggroup = parser.add_mutually_exclusive_group()
    arggroup.add_argument('-k','--ssh_pubkey_file', help='A public key SSH file')
    arggroup.add_argument('-gh', '--github_pubkeys', help='Public keys from a Github account that you want to allow onto the machine')
    args = parser.parse_args()
    # Check the mount point
    if not os.path.exists(args.mntpoint):
        exit(1, f'The mount point provided is not valid.\nMOUNT POINT:\n{args.mntpoint}')
    # Retrieve the public key
    if args.github_pubkeys:
        r = requests.get(f'https://github.com/{args.github_pubkeys}.keys')
        if r.status_code == 200:
            pubkey = r.content.decode()
        else:
            exit(1, 'Could not find the Github user')
    else:
        if not os.path.isfile(args.ssh_pubkey_file):
            exit(1, 'Could not find the public key file')
        with open(args.ssh_pubkey_file, 'r') as file:
            pubkey = file.read()

    # Validate IP, subnet and DNS server
    try:
        ipaddress.ip_address(args.ip_address)
    except ValueError:
        exit(1, 'The IP provided is not valid IP')
    try:
        ipaddress.ip_address(args.subnet)
    except ValueError:
        exit(1, 'The subnet provided is not valid')
    try:
        ipaddress.ip_address(args.dns_server)
    except ValueError:
        exit(1, 'The DNS server provided is not valid IP')
    
    # Read the resolve template
    with open(f'templates/{args.operating_system}/resolv.conf', 'r') as dns_template:
        file_contents = dns_template.read()
        text = re.sub('<DNSSERVER>', args.dns_server, file_contents)
    # Write the resolv file
    with open(f'{args.mntpoint}/etc/resolv.conf', 'w') as dns_target:
        dns_target.write(text)
    
    # Read the interface file template
    with open(f'templates/{args.operating_system}/interfaces', 'r') as interface_template:
        file_contents = interface_template.read()
        text = re.sub('<IPADDRESS>', args.ip_address, file_contents)
        text = re.sub('<SUBNETMASK>', args.subnet, text)
        text = re.sub('<GATEWAY>', args.gateway, text)
        text = re.sub('<DNSSERVER>', args.dns_server, text)
    # Write the interfaces file
    with open(f'{args.mntpoint}/etc/network/interfaces', 'w') as interface_target:
        interface_target.write(text)
    
    # Write the hostname
    with open(f'{args.mntpoint}/etc/hostname', 'w') as interface_target:
        interface_target.write(args.hostname)
    
    # Write the hosts file
    with open(f'templates/{args.operating_system}/hosts', 'r') as hosts_template:
        hosts = hosts_template.read()
        # Sort between hostnames and FQDN
        if args.hostname in '.':
            text = re.sub('<HOSTNAME>', f"{str(args.hostname).split('.')[0]} {args.hostname}", hosts)
        else:
            text = re.sub('<HOSTNAME>', f'{args.hostname}', hosts)    
        with open(f'{args.mntpoint}/etc/hosts', 'w') as hosts_target:
            hosts_target.write(text)
    # Check for .ssh folder in the users home dir
    if not os.path.exists(f'{args.mntpoint}/home/{args.username}/.ssh'):
        os.makedirs(f'{args.mntpoint}/home/{args.username}/.ssh')
    # Write the authorized_keys file
    with open(f'{args.mntpoint}/home/{args.username}/.ssh/authorized_keys', 'w') as authed_keys:
        authed_keys.write(pubkey)

    # Set RO permissions on authorized_keys file
    os.chmod(f'{args.mntpoint}/home/{args.username}/.ssh/authorized_keys', 0o444)
    
    print("Template files written successfully. Eject the SD and happy pi'ing")

It can be run with something like this:

sudo image_customizer.py --ip_address --subnet --dns_server --gateway --hostname  --mntpoint  --github_pubkeys --operating_system --username 
This post is licensed under CC BY 4.0 by the author.