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
- Flash an SD card with the desired operating system as normal with the Rpi imager
- Boot a testing pi with the SD and perform the base configurations
- Plug the USB and external drive into a linux machine Identify the drives with the following command:
1
sudo fdisk -l
- Once you have identified your drives, mount the larger drive to the computer.
- 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
- Shrink the ISO with the pishrink
1 2 3
# sudo pishrink -d <image to shrink> <shrunk image> # Example sudo ./tools/PiShrink/pishrink source destination
- 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