bootbox/builder/ipxe.py
2020-06-18 18:24:06 -04:00

298 lines
13 KiB
Python

#!/usr/bin/env python3
import os
import re
import shutil
import subprocess
##
import git # https://pypi.org/project/GitPython/
import patch_ng # https://pypi.org/project/patch-ng/
patches = ['git-version.patch', 'banner.patch', 'efi-iso.patch']
monkeypatch = {
'config/general.h': {
'enable': [
'NET_PROTO_IPV6',
'DOWNLOAD_PROTO_HTTPS',
'DOWNLOAD_PROTO_FTP',
'DOWNLOAD_PROTO_NFS',
'HTTP_AUTH_NTLM',
# 'HTTP_ENC_PEERDIST',
# 'HTTP_HACK_GCE',
'NSLOOKUP_CMD',
'TIME_CMD',
'DIGEST_CMD',
'LOTEST_CMD',
'VLAN_CMD',
# 'PXE_CMD', # Causes EFI to fail.
'REBOOT_CMD',
'POWEROFF_CMD',
# 'IMAGE_NBI', # I *think* this causes EFI to fail. Can I build it directly as a target w/o enabling?
# 'IMAGE_ELF', # Causes EFI to fail, and is auto-enabled for what needs it.
# 'IMAGE_MULTIBOOT', # Also enabled by default for MBR builds
# 'IMAGE_SCRIPT', # Enabled where needed etc.
# 'IMAGE_BZIMAGE', # http://lists.ipxe.org/pipermail/ipxe-devel/2017-March/005510.html
# 'IMAGE_COMBOOT', # Not really necessary since iPXE has native menus.
# 'IMAGE_EFI', # Enabled by default for EFI builds, and causes build error on bin/ipxe.dsk.tmp
# 'IMAGE_PXE', # EFI builds fail with this. related to PXE_STACK/PXE_CMD?
'IMAGE_TRUST_CMD',
'PCI_CMD',
'PARAM_CMD',
'NEIGHBOUR_CMD',
'PING_CMD',
'CONSOLE_CMD',
'IPSTAT_CMD',
'PROFSTAT_CMD',
'NTP_CMD',
'CERT_CMD'
],
'disable': [
# 'CRYPTO_80211_WEP',
# 'CRYPTO_80211_WPA',
# 'CRYPTO_80211_WPA2',
# 'IWMGMT_CMD'
]},
'config/console.h': {
'enable': [
'CONSOLE_FRAMEBUFFER'
],
'disable': [
# Disables would go here.
]}}
rootdir = '/opt/builds'
logdir = os.path.join(rootdir, 'logs', 'ipxe')
patchdir = os.path.join(rootdir, 'patches', 'ipxe')
buildroot = os.path.join(rootdir, 'ipxe_build')
srcdir = os.path.join(buildroot, 'src')
configdir = os.path.join(rootdir, 'configs', 'ipxe')
destdir = os.path.join(rootdir, 'built', 'ipxe')
repo = git.Repo(buildroot)
master = repo.branches.master
remote = repo.remote('origin')
rom_suffixes = ('rom', 'mrom')
rom_types = ('rtl8139', '8086100e', '80861209', '10500940', '10222000', '10ec8139', '1af41000', '8086100f',
'808610d3', '15ad07b0')
def doMonkeypatch(fname, changeset):
enable_re = None
disable_re = None
fpath = os.path.join(srcdir, fname)
if changeset['enable']:
enable_re = re.compile((r'^\s*(//#define|#undef)\s+'
r'(?P<optname>{0})'
r'(?P<comment>\s+/\*.*)?\s*$').format('|'.join(changeset['enable'])))
if changeset['disable']:
disable_re = re.compile((r'^(#define|//#undef)\s+'
r'(?P<optname>{0})'
r'(?P<comment>\s+/\*.*)?\s*$').format('|'.join(changeset['disable'])))
with open(fpath, 'r') as fh:
configstr = fh.read()
configlines = configstr.splitlines()
for idx, line in enumerate(configlines[:]):
if enable_re:
r = enable_re.search(line)
if r:
configlines[idx] = '#define {0}{1}'.format(r.group('optname'), r.group('comment'))
if disable_re:
r = disable_re.search(line)
if r:
configlines[idx] = '#undef {0}{1}'.format(r.group('optname'), r.group('comment'))
with open(fpath, 'w') as fh:
fh.write('\n'.join(configlines))
fh.write('\n')
return()
def main():
# Cleanup the repo.
repo.head.reset(commit = 'HEAD', working_tree = True)
if repo.active_branch != master:
master.checkout()
repo.head.reset(commit = 'HEAD', working_tree = True)
repo.git.clean('-xdf')
try:
remote.pull()
except BrokenPipeError:
pass
# Patch
for p in patches:
with open(os.path.join(patchdir, p), 'rb') as fh:
patchset = patch_ng.PatchSet(fh)
patchset.apply(strip = 1, root = buildroot)
# "Monkeypatch" - sed-like.
for f, changeset in monkeypatch.items():
doMonkeypatch(f, changeset)
# Build. Finally!
# TODO: ARM support!
# for d in ('default', 'efi', 'iso', 'legacy', 'roms', 'arm'):
for d in ('default', 'efi', 'iso', 'legacy', 'roms'):
dpath = os.path.join(destdir, d)
os.makedirs(dpath, exist_ok = True)
os.makedirs(logdir, exist_ok = True)
os.chdir(srcdir)
## Base files
# TODO: ARM support!
# TODO: efi-sb support (secureboot)!
# http://ipxe.org/appnote/buildtargets
# http://ipxe.org/appnote/buildtargets#special_targets
## BOOTSTRAP ##
with open(os.path.join(logdir, 'all_bootstrap.stderr'), 'wb') as stderr, \
open(os.path.join(logdir, 'all_bootstrap.stdout'), 'wb') as stdout:
subprocess.run(['make',
'all',
'EMBED={0}'.format(os.path.join(configdir, 'bootstrap.ipxe'))],
stdout = stdout,
stderr = stderr)
for fsrc, fdest in (('undionly.kpxe', 'legacy/bootstrap_{f}'),
('ipxe.iso', 'iso/bootstrap_bios.iso'),
('ipxe.usb', 'iso/bootstrap_pxe_usb.img'),
('ipxe.dsk', 'iso/bootstrap_pxe_floppy.img')):
srcpath = os.path.join(srcdir, 'bin', fsrc)
fname = os.path.basename(srcpath)
destpath = os.path.join(destdir, fdest.format(f = fname))
shutil.copy2(srcpath, destpath)
for rom in rom_types:
for s in rom_suffixes:
fname = '{0}.{1}'.format(rom, s)
fpath = os.path.join(srcdir, 'bin', fname)
if os.path.isfile(fpath):
shutil.copy2(fpath,
os.path.join(destdir, 'roms', 'bootstrap_{0}'.format(fname)))
# http://ipxe.org/howto/romburning
# https://libvirt.org/formatdomain.html#elementsNICSROM
if rom == '1af41000':
os.symlink(fpath,
os.path.join(destdir, 'roms', 'bootstrap_virtio.rom'))
## EMBEDDED ##
with open(os.path.join(logdir, 'all.stderr'), 'wb') as stderr, \
open(os.path.join(logdir, 'all.stdout'), 'wb') as stdout:
subprocess.run(['make',
'all',
'EMBED={0}'.format(os.path.join(configdir, 'chain-default.ipxe'))],
stdout = stdout,
stderr = stderr)
for fsrc, fdest in (('undionly.kpxe', 'legacy/{f}'),
('ipxe.iso', 'iso/bios.iso'),
('ipxe.usb', 'iso/pxe_usb.img'),
('ipxe.dsk', 'iso/pxe_floppy.img')):
srcpath = os.path.join(srcdir, 'bin', fsrc)
fname = os.path.basename(srcpath)
destpath = os.path.join(destdir, fdest.format(f = fname))
shutil.copy2(srcpath, destpath)
for rom in rom_types:
for s in rom_suffixes:
fname = '{0}.{1}'.format(rom, s)
fpath = os.path.join(srcdir, 'bin', fname)
if os.path.isfile(fpath):
shutil.copy2(fpath,
os.path.join(destdir, 'roms', fname))
if rom == '1af41000':
os.symlink(fpath,
os.path.join(destdir, 'roms', 'virtio.rom'))
# DOS/MBR sector/HDD img.
## BOOTSTRAP ##
with open(os.path.join(logdir, 'bootstrap_hdd.stderr'), 'wb') as stderr, \
open(os.path.join(logdir, 'bootstrap_hdd.stdout'), 'wb') as stdout:
subprocess.run(['make',
'bin/ipxe.hd',
'EMBED={0}'.format(os.path.join(configdir, 'bootstrap.ipxe'))],
stdout = stdout,
stderr = stderr)
shutil.copy2(os.path.join(srcdir, 'bin/ipxe.hd'),
os.path.join(destdir, 'iso', 'bootstrap_legacy_mbr.img'))
## EMBEDDED ##
with open(os.path.join(logdir, 'hdd.stderr'), 'wb') as stderr, \
open(os.path.join(logdir, 'hdd.stdout'), 'wb') as stdout:
subprocess.run(['make',
'bin/ipxe.hd',
'EMBED={0}'.format(os.path.join(configdir, 'chain-default.ipxe'))],
stdout = stdout,
stderr = stderr)
shutil.copy2(os.path.join(srcdir, 'bin/ipxe.hd'),
os.path.join(destdir, 'iso', 'legacy_mbr.img'))
# PXE loaders
## BOOTSTRAP ##
with open(os.path.join(logdir, 'bootstrap_loader.stderr'), 'wb') as stderr, \
open(os.path.join(logdir, 'bootstrap_loader.stdout'), 'wb') as stdout:
subprocess.run(['make',
'bin/ipxe.pxe',
'EMBED={0}'.format(os.path.join(configdir, 'bootstrap.ipxe'))],
stdout = stdout,
stderr = stderr)
os.rename(os.path.join(srcdir, 'bin/ipxe.pxe'),
os.path.join(destdir, 'default', 'bootstrap_loader.pxe'))
## EMBEDDED ##
with open(os.path.join(logdir, 'loader.stderr'), 'wb') as stderr, \
open(os.path.join(logdir, 'loader.stdout'), 'wb') as stdout:
subprocess.run(['make',
'bin/ipxe.pxe',
'EMBED={0}'.format(os.path.join(configdir, 'chain-default.ipxe'))],
stdout = stdout,
stderr = stderr)
os.rename(os.path.join(srcdir, 'bin/ipxe.pxe'),
os.path.join(destdir, 'default', 'loader.pxe'))
# EFI binaries and ISO images
# These have to be done grouped because the eiso stuff APPARENTLY doesn't parse EMBED or lack thereof correctly?
## BOOTSTRAP ##
### EFI ###
with open(os.path.join(logdir, 'bootstrap_efi.stderr'), 'wb') as stderr, \
open(os.path.join(logdir, 'bootstrap_efi.stdout'), 'wb') as stdout:
subprocess.run(['make',
'bin-i386-efi/ipxe.efi',
'bin-x86_64-efi/ipxe.efi',
'EMBED={0}'.format(os.path.join(configdir, 'bootstrap.ipxe'))],
stdout = stdout,
stderr = stderr)
shutil.copy2(os.path.join(srcdir, 'bin-i386-efi/ipxe.efi'),
os.path.join(destdir, 'efi', 'bootstrap_32.efi'))
shutil.copy2(os.path.join(srcdir, 'bin-x86_64-efi/ipxe.efi'),
os.path.join(destdir, 'efi', 'bootstrap_64.efi'))
### UEFI ISO ###
with open(os.path.join(logdir, 'bootstrap_iso.stderr'), 'wb') as stderr, \
open(os.path.join(logdir, 'bootstrap_iso.stdout'), 'wb') as stdout:
subprocess.run(['make',
'bin/ipxe.liso',
'bin/ipxe.eiso',
'EMBED={0}'.format(os.path.join(configdir, 'bootstrap.ipxe'))],
stdout = stdout,
stderr = stderr)
os.rename(os.path.join(srcdir, 'bin/ipxe.liso'),
os.path.join(destdir, 'iso', 'bootstrap_legacy.iso'))
os.rename(os.path.join(srcdir, 'bin/ipxe.eiso'),
os.path.join(destdir, 'iso', 'bootstrap_uefi.iso'))
## EMBEDDED ##
### EFI ###
with open(os.path.join(logdir, 'efi.stderr'), 'wb') as stderr, \
open(os.path.join(logdir, 'efi.stdout'), 'wb') as stdout:
subprocess.run(['make',
'bin-i386-efi/ipxe.efi',
'bin-x86_64-efi/ipxe.efi',
'EMBED={0}'.format(os.path.join(configdir, 'chain-default.ipxe'))],
stdout = stdout,
stderr = stderr)
shutil.copy2(os.path.join(srcdir, 'bin-i386-efi/ipxe.efi'),
os.path.join(destdir, 'efi', '32.efi'))
shutil.copy2(os.path.join(srcdir, 'bin-x86_64-efi/ipxe.efi'),
os.path.join(destdir, 'efi', '64.efi'))
### UEFI ISO ###
with open(os.path.join(logdir, 'iso.stderr'), 'wb') as stderr, \
open(os.path.join(logdir, 'iso.stdout'), 'wb') as stdout:
subprocess.run(['make',
'bin/ipxe.liso',
'bin/ipxe.eiso',
'EMBED={0}'.format(os.path.join(configdir, 'chain-default.ipxe'))],
stdout = stdout,
stderr = stderr)
os.rename(os.path.join(srcdir, 'bin/ipxe.liso'),
os.path.join(destdir, 'iso', 'legacy.iso'))
os.rename(os.path.join(srcdir, 'bin/ipxe.eiso'),
os.path.join(destdir, 'iso', 'uefi.iso'))
return()
if __name__ == '__main__':
main()