think i might have something working. ipxe untested still.

This commit is contained in:
brent s. 2016-12-17 01:07:50 -05:00
parent 77590ef0a0
commit a670ff8eb7
11 changed files with 238 additions and 115 deletions

View File

@ -1,4 +1,5 @@
import os import os
from io import BytesIO
import subprocess import subprocess
import datetime import datetime
import jinja2 import jinja2
@ -8,8 +9,10 @@ import psutil
def genGPG(conf): def genGPG(conf):
# https://media.readthedocs.org/pdf/pygpgme/latest/pygpgme.pdf # https://media.readthedocs.org/pdf/pygpgme/latest/pygpgme.pdf
build = conf['build'] build = conf['build']
bdisk = conf['bdisk']
gpghome = conf['gpg']['mygpghome'] gpghome = conf['gpg']['mygpghome']
distkey = build['gpgkey'] distkey = build['gpgkey']
gpgkeyserver = build['gpgkeyserver']
templates_dir = '{0}/extra/templates'.format(build['basedir']) templates_dir = '{0}/extra/templates'.format(build['basedir'])
mykey = False mykey = False
pkeys = [] pkeys = []
@ -26,14 +29,16 @@ def genGPG(conf):
os.environ['GNUPGHOME'] = gpghome os.environ['GNUPGHOME'] = gpghome
gpg = gpgme.Context() gpg = gpgme.Context()
# do we need to add a keyserver? # do we need to add a keyserver?
if build['gpgkeyserver'] != '': if gpgkeyserver != '':
dirmgr = '{0}/dirmngr.conf'.format(gpghome) dirmgr = '{0}/dirmngr.conf'.format(gpghome)
if os.path.isfile(dirmgr): if os.path.isfile(dirmgr):
with open(dirmgr, 'r+') as f: with open(dirmgr, 'r+') as f:
findme = any(gpgmirror in line for line in f) findme = any(gpgkeyserver in line for line in f)
if not findme: if not findme:
f.seek(0, os.SEEK_END) f.seek(0, os.SEEK_END)
f.write("\n# Added by {0}.\nkeyserver {1}\n") f.write("\n# Added by {0}.\nkeyserver {1}\n".format(
bdisk['pname'],
gpgkeyserver))
if mykey: if mykey:
try: try:
privkey = gpg.get_key(mykey, True) privkey = gpg.get_key(mykey, True)
@ -58,22 +63,28 @@ def genGPG(conf):
if build['gpgkeyserver'] != '': if build['gpgkeyserver'] != '':
dirmgr = '{0}/dirmngr.conf'.format(gpghome) dirmgr = '{0}/dirmngr.conf'.format(gpghome)
with open(dirmgr, 'r+') as f: with open(dirmgr, 'r+') as f:
findme = any(gpgmirror in line for line in f) findme = any(gpgkeyserver in line for line in f)
if not findme: if not findme:
f.seek(0, os.SEEK_END) f.seek(0, os.SEEK_END)
f.write("\n# Added by {0}.\nkeyserver {1}\n" f.write("\n# Added by {0}.\nkeyserver {1}\n".format(
bdisk['pname'],
build['gpgkeyserver']))
gpg.signers = pkeys gpg.signers = pkeys
# Now we try to find and add the key for the base image. # Now we try to find and add the key for the base image.
gpg.keylist_mode = 2 # remote (keyserver) gpg.keylist_mode = gpgme.KEYLIST_MODE_EXTERN # remote (keyserver)
try: if distkey: # testing
#try:
key = gpg.get_key(distkey) key = gpg.get_key(distkey)
except: #except:
exit('{0}: ERROR: We cannot find key ID {1}!'.format( # exit('{0}: ERROR: We cannot find key ID {1}!'.format(
# datetime.datetime.now(),
# distkey))
importkey = key.subkeys[0].fpr
gpg.keylist_mode = gpgme.KEYLIST_MODE_LOCAL # local keyring (default)
DEVNULL = open(os.devnull, 'w')
print('{0}: [GPG] Importing {1} and signing it for verification purposes...'.format(
datetime.datetime.now(), datetime.datetime.now(),
distkey)) distkey))
importkey = key.subkeys[0].fpr
gpg.keylist_mode = 1 # local keyring (default)
DEVNULL = open(os.devnull, 'w')
cmd = ['/usr/bin/gpg', cmd = ['/usr/bin/gpg',
'--recv-keys', '--recv-keys',
'--batch', '--batch',
@ -110,49 +121,97 @@ def killStaleAgent(conf):


def signIMG(path, conf): def signIMG(path, conf):
if conf['build']['gpg']: if conf['build']['gpg']:
# If we enabled GPG signing, we need to figure out if we # Do we want to kill off any stale gpg-agents? (So we spawn a new one)
# are using a personal key or the automatically generated one. # Requires further testing.
if conf['gpg']['mygpghome'] != '': #killStaleAgent()
gpghome = conf['gpg']['mygpghome'] gpg = conf['gpgobj']
else: print('{0}: [GPG] Signing {1}...'.format(
gpghome = conf['build']['dlpath'] + '/.gnupg'
if conf['gpg']['mygpgkey'] != '':
keyid = conf['gpg']['mygpgkey']
else:
keyid = False
# We want to kill off any stale gpg-agents so we spawn a new one.
killStaleAgent()
## HERE BE DRAGONS. Converting to PyGPGME...
# List of Key instances used for signing with sign() and encrypt_sign().
gpg = gpgme.Context()
if keyid:
gpg.signers = gpg.get_key(keyid)
else:
# Try to "guess" the key ID.
# If we got here, it means we generated a key earlier during the tarball download...
# So we can use that!
pass
# And if we didn't specify one manually, we'll pick the first one we find.
# This way we can use the automatically generated one from prep.
if not keyid:
keyid = gpg.list_keys(True)[0]['keyid']
print('{0}: [BUILD] Signing {1} with {2}...'.format(
datetime.datetime.now(), datetime.datetime.now(),
path, path))
keyid)) # May not be necessary; further testing necessary
# TODO: remove this warning when upstream python-gnupg fixes #if os.getenv('GPG_AGENT_INFO'):
print('\t\t\t If you see a "ValueError: Unknown status message: \'KEY_CONSIDERED\'" error, ' + # del os.environ['GPG_AGENT_INFO']
'it can be safely ignored.') gpg = conf['gpgobj']
print('\t\t\t If this is taking a VERY LONG time, try installing haveged and starting it. ' + # ASCII-armor (.asc)
'This can be done safely in parallel with the build process.') gpg.armor = True
data_in = open(path, 'rb') data_in = open(path, 'rb')
gpg.sign_file(data_in, keyid = keyid, detach = True, sigbuf = BytesIO()
clearsign = False, output = '{0}.sig'.format(path)) sig = gpg.sign(data_in, sigbuf, gpgme.SIG_MODE_DETACH)
_ = sigbuf.seek(0)
_ = data_in.seek(0)
data_in.close() data_in.close()
with open('{0}.asc'.format(path), 'wb') as f:
f.write(sigbuf.read())
print('{0}: [GPG] Wrote {1}.asc (ASCII-armored signature).'.format(
datetime.datetime.now(),
path))
# Binary signature (.sig)
gpg.armor = False
data_in = open(path, 'rb')
sigbuf = BytesIO()
sig = gpg.sign(data_in, sigbuf, gpgme.SIG_MODE_DETACH)
_ = sigbuf.seek(0)
_ = data_in.seek(0)
data_in.close()
with open('{0}.sig'.format(path), 'wb') as f:
f.write(sigbuf.read())
print('{0}: [GPG] Wrote {1}.sig (binary signature).'.format(
datetime.datetime.now(),
path))


def gpgVerify(sigfile, datafile, conf): def gpgVerify(sigfile, datafile, conf):
gpg = conf['gpgobj']
fullkeys = []
print('{0}: [GPG] Verifying {1} with {2}...'.format(
datetime.datetime.now(),
datafile,
sigfile))
keylst = gpg.keylist()
for k in keylst:
fullkeys.append(k.subkeys[0].fpr)
with open(sigfile,'rb') as s:
with open(datafile, 'rb') as f:
sig = gpg.verify(s, f, None)
for x in sig:
if x.validity <= 1:
if not x.validity_reason:
reason = 'we require a signature trust of 2 or higher'
else:
reason = x.validity_reason
print('{0}: [GPG] Key {1} failed to verify: {2}'.format(
datetime.datetime.now(),
x.fpr,
reason))
verified = False
skeys = []
for k in sig:
skeys.append(k.fpr)
if k.fpr in fullkeys:
verified = True
break
else:
pass pass
if verified:
print('{0}: [GPG] {1} verified (success).'.format(
datetime.datetime.now(),
datafile))
else:
print('{0}: [GPG] {1} failed verification!'.format(
datetime.datetime.now(),
datafile))
return(verified)


def delTempKeys(conf): def delTempKeys(conf):
pass # Create a config option to delete these.
# It's handy to keep these keys, but I'd understand if
# people didn't want to use them.
gpg = conf['gpgobj']
if conf['gpg']:
keys = []
if conf['gpgkey'] != '':
keys.append(gpg.get_key(conf['gpgkey']))
if conf['mygpghome'] == '':
keys.append(gpg.get_key(None, True)) # this is safe; we generated our own
for k in keys:
gpg.delete(k)
killStaleAgent(conf) killStaleAgent(conf)

View File

@ -18,9 +18,10 @@ if __name__ == '__main__':
conf = host.parseConfig(host.getConfig())[1] conf = host.parseConfig(host.getConfig())[1]
prep.dirChk(conf) prep.dirChk(conf)
conf['gpgobj'] = bGPG.genGPG(conf) conf['gpgobj'] = bGPG.genGPG(conf)
prep.buildChroot(conf['build'], keep = False) prep.buildChroot(conf, keep = False)
prep.prepChroot(conf['build'], conf['bdisk'], conf['user']) prep.prepChroot(conf)
arch = conf['build']['arch'] arch = conf['build']['arch']
#bGPG.killStaleAgent(conf)
for a in arch: for a in arch:
bchroot.chroot(conf['build']['chrootdir'] + '/root.' + a, 'bdisk.square-r00t.net') bchroot.chroot(conf['build']['chrootdir'] + '/root.' + a, 'bdisk.square-r00t.net')
bchroot.chrootUnmount(conf['build']['chrootdir'] + '/root.' + a) bchroot.chrootUnmount(conf['build']['chrootdir'] + '/root.' + a)
@ -29,7 +30,7 @@ if __name__ == '__main__':
build.genImg(conf) build.genImg(conf)
build.genUEFI(conf['build'], conf['bdisk']) build.genUEFI(conf['build'], conf['bdisk'])
fulliso = build.genISO(conf) fulliso = build.genISO(conf)
build.signIMG(fulliso['Main']['file'], conf) bGPG.signIMG(fulliso['Main']['file'], conf)
build.displayStats(fulliso) build.displayStats(fulliso)
if conf['build']['ipxe']: if conf['build']['ipxe']:
bSSL.sslPKI(conf) bSSL.sslPKI(conf)
@ -39,7 +40,7 @@ if __name__ == '__main__':
for x in iso.keys(): for x in iso.keys():
if x != 'name': if x != 'name':
path = iso[x]['file'] path = iso[x]['file']
build.signIMG(path, conf) bGPG.signIMG(path, conf)
build.displayStats(iso) build.displayStats(iso)
bsync.http(conf) bsync.http(conf)
bsync.tftp(conf) bsync.tftp(conf)

View File

@ -10,7 +10,7 @@ import subprocess
def http(conf): def http(conf):
http = conf['http'] http = conf['http']
build = conf['build'] build = conf['build']
tempdir = build['tempdir'] prepdir = build['prepdir']
arch = build['arch'] arch = build['arch']
bdisk = conf['bdisk'] bdisk = conf['bdisk']
if conf['sync']['http']: if conf['sync']['http']:
@ -48,7 +48,7 @@ def http(conf):
fulldest = '{0}/{1}'.format(httpdir, destpath) fulldest = '{0}/{1}'.format(httpdir, destpath)
parentdir = os.path.split(fulldest)[0] parentdir = os.path.split(fulldest)[0]
os.makedirs(parentdir, exist_ok = True) os.makedirs(parentdir, exist_ok = True)
shutil.copy2('{0}/{1}'.format(tempdir, k), '{0}/{1}'.format(httpdir, httpfiles[k])) shutil.copy2('{0}/{1}'.format(prepdir, k), '{0}/{1}'.format(httpdir, httpfiles[k]))
for root, dirs, files in os.walk(httpdir): for root, dirs, files in os.walk(httpdir):
for d in dirs: for d in dirs:
os.chown(os.path.join(root, d), uid, gid) os.chown(os.path.join(root, d), uid, gid)
@ -59,7 +59,7 @@ def tftp(conf):
# TODO: pxelinux cfg # TODO: pxelinux cfg
tftp = conf['tftp'] tftp = conf['tftp']
build = conf['build'] build = conf['build']
tempdir = build['tempdir'] prepdir = build['prepdir']
arch = build['arch'] arch = build['arch']
bdisk = conf['bdisk'] bdisk = conf['bdisk']
if conf['sync']['tftp']: if conf['sync']['tftp']:
@ -96,7 +96,7 @@ def tftp(conf):
fulldest = '{0}/{1}'.format(tftpdir, destpath) fulldest = '{0}/{1}'.format(tftpdir, destpath)
parentdir = os.path.split(fulldest)[0] parentdir = os.path.split(fulldest)[0]
os.makedirs(parentdir, exist_ok = True) os.makedirs(parentdir, exist_ok = True)
shutil.copy2('{0}/{1}'.format(tempdir, k), '{0}/{1}'.format(tftpdir, tftpfiles[k])) shutil.copy2('{0}/{1}'.format(prepdir, k), '{0}/{1}'.format(tftpdir, tftpfiles[k]))
for root, dirs, files in os.walk(tftpdir): for root, dirs, files in os.walk(tftpdir):
for d in dirs: for d in dirs:
os.chown(os.path.join(root, d), uid, gid) os.chown(os.path.join(root, d), uid, gid)
@ -121,7 +121,7 @@ def rsync(conf):
# and do nothing if http- copying over three copies of the squashed filesystems # and do nothing if http- copying over three copies of the squashed filesystems
# is a waste of time, bandwidth, and disk space on target. # is a waste of time, bandwidth, and disk space on target.
build = conf['build'] build = conf['build']
tempdir = build['tempdir'] prepdir = build['prepdir']
isodir = build['isodir'] isodir = build['isodir']
arch = build['arch'] arch = build['arch']
rsync = conf['rsync'] rsync = conf['rsync']
@ -159,7 +159,7 @@ def rsync(conf):
cmd[4], cmd[4],
server)) server))
subprocess.call(cmd) subprocess.call(cmd)
cmd[4] = '{0}/boot'.format(build['tempdir']) cmd[4] = '{0}/boot'.format(build['prepdir'])
subprocess.call(cmd) subprocess.call(cmd)
if conf['rsync']['iso']: if conf['rsync']['iso']:
cmd[4] = isodir cmd[4] = isodir
@ -170,7 +170,7 @@ def rsync(conf):
subprocess.call(cmd) subprocess.call(cmd)
# Now we copy some extra files. # Now we copy some extra files.
prebuild_dir = '{0}/extra/pre-build.d'.format(build['basedir']) prebuild_dir = '{0}/extra/pre-build.d'.format(build['basedir'])
rsync_files = ['{0}/VERSION_INFO.txt'.format(tempdir), rsync_files = ['{0}/VERSION_INFO.txt'.format(prepdir),
'{0}/root/packages.both'.format(prebuild_dir), '{0}/root/packages.both'.format(prebuild_dir),
'{0}/root/iso.pkgs.both'.format(prebuild_dir)] '{0}/root/iso.pkgs.both'.format(prebuild_dir)]
for x in rsync_files: for x in rsync_files:

View File

@ -8,6 +8,7 @@ import psutil
import jinja2 import jinja2
import humanize import humanize
import datetime import datetime
import bGPG # bdisk.bGPG
from urllib.request import urlopen from urllib.request import urlopen




@ -18,7 +19,7 @@ def genImg(conf):
chrootdir = build['chrootdir'] chrootdir = build['chrootdir']
archboot = build['archboot'] archboot = build['archboot']
basedir = build['basedir'] basedir = build['basedir']
tempdir = build['tempdir'] prepdir = build['prepdir']
hashes = {} hashes = {}
hashes['sha256'] = {} hashes['sha256'] = {}
hashes['md5'] = {} hashes['md5'] = {}
@ -71,11 +72,11 @@ def genImg(conf):
squashfses.append('{0}'.format(squashimg)) squashfses.append('{0}'.format(squashimg))
print("{0}: [BUILD] Hash checksums complete.".format(datetime.datetime.now())) print("{0}: [BUILD] Hash checksums complete.".format(datetime.datetime.now()))
# Logo # Logo
os.makedirs(tempdir + '/boot', exist_ok = True) os.makedirs(prepdir + '/boot', exist_ok = True)
if not os.path.isfile('{0}/extra/{1}.png'.format(basedir, bdisk['uxname'])): if not os.path.isfile('{0}/extra/{1}.png'.format(basedir, bdisk['uxname'])):
shutil.copy2(basedir + '/extra/bdisk.png', '{0}/{1}.png'.format(tempdir, bdisk['uxname'])) shutil.copy2(basedir + '/extra/bdisk.png', '{0}/{1}.png'.format(prepdir, bdisk['uxname']))
else: else:
shutil.copy2(basedir + '/extra/{0}.png'.format(bdisk['uxname']), '{0}/{1}.png'.format(tempdir, bdisk['uxname'])) shutil.copy2(basedir + '/extra/{0}.png'.format(bdisk['uxname']), '{0}/{1}.png'.format(prepdir, bdisk['uxname']))
# Kernels, initrds... # Kernels, initrds...
# We use a dict here so we can use the right filenames... # We use a dict here so we can use the right filenames...
# I might change how I handle this in the future. # I might change how I handle this in the future.
@ -83,9 +84,9 @@ def genImg(conf):
bootfiles['kernel'] = ['vmlinuz-linux-' + bdisk['name'], '{0}.{1}.kern'.format(bdisk['uxname'], bitness)] bootfiles['kernel'] = ['vmlinuz-linux-' + bdisk['name'], '{0}.{1}.kern'.format(bdisk['uxname'], bitness)]
bootfiles['initrd'] = ['initramfs-linux-{0}.img'.format(bdisk['name']), '{0}.{1}.img'.format(bdisk['uxname'], bitness)] bootfiles['initrd'] = ['initramfs-linux-{0}.img'.format(bdisk['name']), '{0}.{1}.img'.format(bdisk['uxname'], bitness)]
for x in ('kernel', 'initrd'): for x in ('kernel', 'initrd'):
shutil.copy2('{0}/root.{1}/boot/{2}'.format(chrootdir, a, bootfiles[x][0]), '{0}/boot/{1}'.format(tempdir, bootfiles[x][1])) shutil.copy2('{0}/root.{1}/boot/{2}'.format(chrootdir, a, bootfiles[x][0]), '{0}/boot/{1}'.format(prepdir, bootfiles[x][1]))
for i in squashfses: for i in squashfses:
signIMG(i, conf) bGPG.signIMG(i, conf)




def genUEFI(build, bdisk): def genUEFI(build, bdisk):
@ -95,19 +96,20 @@ def genUEFI(build, bdisk):
# Plus there's always multiarch. # Plus there's always multiarch.
# I can probably do this better with a dict... TODO. # I can probably do this better with a dict... TODO.
if 'x86_64' in arch: if 'x86_64' in arch:
tempdir = build['tempdir'] prepdir = build['prepdir']
basedir = build['basedir'] basedir = build['basedir']
chrootdir = build['chrootdir'] chrootdir = build['chrootdir']
mountpt = build['mountpt'] mountpt = build['mountpt']
templates_dir = build['basedir'] + '/extra/templates' templates_dir = build['basedir'] + '/extra/templates'
efidir = '{0}/EFI/{1}'.format(tempdir, bdisk['name']) efidir = '{0}/EFI/{1}'.format(prepdir, bdisk['name'])
os.makedirs(efidir, exist_ok = True) os.makedirs(efidir, exist_ok = True)
efiboot_img = efidir + '/efiboot.img' efiboot_img = efidir + '/efiboot.img'
os.makedirs(tempdir + '/EFI/boot', exist_ok = True) os.makedirs(prepdir + '/EFI/boot', exist_ok = True)
os.makedirs(efidir, exist_ok = True)
## Download the EFI shells if we don't have them. ## Download the EFI shells if we don't have them.
# For UEFI 2.3+ (http://sourceforge.net/apps/mediawiki/tianocore/index.php?title=UEFI_Shell) # For UEFI 2.3+ (http://sourceforge.net/apps/mediawiki/tianocore/index.php?title=UEFI_Shell)
if not os.path.isfile(tempdir + '/EFI/shellx64_v2.efi'): if not os.path.isfile(prepdir + '/EFI/shellx64_v2.efi'):
shell2_path = tempdir + '/EFI/shellx64_v2.efi' shell2_path = prepdir + '/EFI/shellx64_v2.efi'
print("{0}: [BUILD] Warning: You are missing {1}. Fetching...".format(datetime.datetime.now(), shell2_path)) print("{0}: [BUILD] Warning: You are missing {1}. Fetching...".format(datetime.datetime.now(), shell2_path))
shell2_url = 'https://raw.githubusercontent.com/tianocore/edk2/master/ShellBinPkg/UefiShell/X64/Shell.efi' shell2_url = 'https://raw.githubusercontent.com/tianocore/edk2/master/ShellBinPkg/UefiShell/X64/Shell.efi'
shell2_fetch = urlopen(shell2_url) shell2_fetch = urlopen(shell2_url)
@ -116,8 +118,8 @@ def genUEFI(build, bdisk):
shell2_fetch.close() shell2_fetch.close()
# Shell for older versions (http://sourceforge.net/apps/mediawiki/tianocore/index.php?title=Efi-shell) # Shell for older versions (http://sourceforge.net/apps/mediawiki/tianocore/index.php?title=Efi-shell)
# TODO: is there an Arch package for this? can we just install that in the chroot and copy the shell binaries? # TODO: is there an Arch package for this? can we just install that in the chroot and copy the shell binaries?
if not os.path.isfile(tempdir + '/EFI/shellx64_v1.efi'): if not os.path.isfile(prepdir + '/EFI/shellx64_v1.efi'):
shell1_path = tempdir + '/EFI/shellx64_v1.efi' shell1_path = prepdir + '/EFI/shellx64_v1.efi'
print("{0}: [BUILD] Warning: You are missing {1}. Fetching...".format(datetime.datetime.now(), shell1_path)) print("{0}: [BUILD] Warning: You are missing {1}. Fetching...".format(datetime.datetime.now(), shell1_path))
shell1_url = 'https://raw.githubusercontent.com/tianocore/edk2/master/EdkShellBinPkg/FullShell/X64/Shell_Full.efi' shell1_url = 'https://raw.githubusercontent.com/tianocore/edk2/master/EdkShellBinPkg/FullShell/X64/Shell_Full.efi'
shell1_fetch = urlopen(shell1_url) shell1_fetch = urlopen(shell1_url)
@ -133,20 +135,20 @@ def genUEFI(build, bdisk):
fname = 'bootx64.efi' fname = 'bootx64.efi'
else: else:
fname = f fname = f
if not os.path.isfile(tempdir + '/EFI/boot/' + fname): if not os.path.isfile(prepdir + '/EFI/boot/' + fname):
url = shim_url + f url = shim_url + f
url_fetch = urlopen(url) url_fetch = urlopen(url)
with open(tempdir + '/EFI/boot/' + fname, 'wb+') as dl: with open(prepdir + '/EFI/boot/' + fname, 'wb+') as dl:
dl.write(url_fetch.read()) dl.write(url_fetch.read())
url_fetch.close() url_fetch.close()
# And we also need the systemd efi bootloader. # And we also need the systemd efi bootloader.
if os.path.isfile(tempdir + '/EFI/boot/loader.efi'): if os.path.isfile(prepdir + '/EFI/boot/loader.efi'):
os.remove(tempdir + '/EFI/boot/loader.efi') os.remove(prepdir + '/EFI/boot/loader.efi')
shutil.copy2(chrootdir + '/root.x86_64/usr/lib/systemd/boot/efi/systemd-bootx64.efi', tempdir + '/EFI/boot/loader.efi') shutil.copy2(chrootdir + '/root.x86_64/usr/lib/systemd/boot/efi/systemd-bootx64.efi', prepdir + '/EFI/boot/loader.efi')
# And the accompanying configs for the systemd efi bootloader, too. # And the accompanying configs for the systemd efi bootloader, too.
tpl_loader = jinja2.FileSystemLoader(templates_dir) tpl_loader = jinja2.FileSystemLoader(templates_dir)
env = jinja2.Environment(loader = tpl_loader) env = jinja2.Environment(loader = tpl_loader)
os.makedirs(tempdir + '/loader/entries', exist_ok = True) os.makedirs(prepdir + '/loader/entries', exist_ok = True)
for t in ('loader', 'ram', 'base', 'uefi2', 'uefi1'): for t in ('loader', 'ram', 'base', 'uefi2', 'uefi1'):
if t == 'base': if t == 'base':
fname = bdisk['uxname'] + '.conf' fname = bdisk['uxname'] + '.conf'
@ -155,10 +157,10 @@ def genUEFI(build, bdisk):
else: else:
fname = bdisk['uxname'] + '_' + t + '.conf' fname = bdisk['uxname'] + '_' + t + '.conf'
if t == 'loader': if t == 'loader':
tplpath = tempdir + '/loader/' tplpath = prepdir + '/loader/'
fname = 'loader.conf' # we change the var from above because it's an oddball. fname = 'loader.conf' # we change the var from above because it's an oddball.
else: else:
tplpath = tempdir + '/loader/entries/' tplpath = prepdir + '/loader/entries/'
tpl = env.get_template('EFI/' + t + '.conf.j2') tpl = env.get_template('EFI/' + t + '.conf.j2')
tpl_out = tpl.render(build = build, bdisk = bdisk) tpl_out = tpl.render(build = build, bdisk = bdisk)
with open(tplpath + fname, "w+") as f: with open(tplpath + fname, "w+") as f:
@ -176,9 +178,9 @@ def genUEFI(build, bdisk):
'/EFI/shellx64_v1.efi', '/EFI/shellx64_v1.efi',
'/EFI/shellx64_v2.efi'] '/EFI/shellx64_v2.efi']
for i in sizefiles: for i in sizefiles:
sizetotal += os.path.getsize(tempdir + i) sizetotal += os.path.getsize(prepdir + i)
# Loader configs # Loader configs
for (path, dirs, files) in os.walk(tempdir + '/loader/'): for (path, dirs, files) in os.walk(prepdir + '/loader/'):
for file in files: for file in files:
fname = os.path.join(path, file) fname = os.path.join(path, file)
sizetotal += os.path.getsize(fname) sizetotal += os.path.getsize(fname)
@ -223,13 +225,13 @@ def genUEFI(build, bdisk):
with open(tplpath + fname, "w+") as f: with open(tplpath + fname, "w+") as f:
f.write(tpl_out) f.write(tpl_out)
for x in ('bootx64.efi', 'HashTool.efi', 'loader.efi'): for x in ('bootx64.efi', 'HashTool.efi', 'loader.efi'):
y = tempdir + '/EFI/boot/' + x y = prepdir + '/EFI/boot/' + x
z = mountpt + '/EFI/boot/' + x z = mountpt + '/EFI/boot/' + x
if os.path.isfile(z): if os.path.isfile(z):
os.remove(z) os.remove(z)
shutil.copy(y, z) shutil.copy(y, z)
for x in ('shellx64_v1.efi', 'shellx64_v2.efi'): for x in ('shellx64_v1.efi', 'shellx64_v2.efi'):
y = tempdir + '/EFI/' + x y = prepdir + '/EFI/' + x
z = mountpt + '/EFI/' + x z = mountpt + '/EFI/' + x
if os.path.isfile(z): if os.path.isfile(z):
os.remove(z) os.remove(z)
@ -253,16 +255,16 @@ def genISO(conf):
build = conf['build'] build = conf['build']
bdisk = conf['bdisk'] bdisk = conf['bdisk']
archboot = build['archboot'] archboot = build['archboot']
tempdir = build['tempdir'] prepdir = build['prepdir']
templates_dir = build['basedir'] + '/extra/templates' templates_dir = build['basedir'] + '/extra/templates'
arch = build['arch'] arch = build['arch']
builddir = tempdir + '/' + bdisk['name'] builddir = prepdir + '/' + bdisk['name']
extradir = build['basedir'] + '/extra/' extradir = build['basedir'] + '/extra/'
# arch[0] is safe to use, even if multiarch, because the only cases when it'd be ambiguous # arch[0] is safe to use, even if multiarch, because the only cases when it'd be ambiguous
# is when x86_64 is specifically set to [0]. See host.py's parseConfig(). # is when x86_64 is specifically set to [0]. See host.py's parseConfig().
# TODO: can we use syslinux for EFI too instead of prebootloader? # TODO: can we use syslinux for EFI too instead of prebootloader?
syslinuxdir = build['chrootdir'] + '/root.' + arch[0] + '/usr/lib/syslinux/bios/' syslinuxdir = build['chrootdir'] + '/root.' + arch[0] + '/usr/lib/syslinux/bios/'
sysl_tmp = tempdir + '/isolinux/' sysl_tmp = prepdir + '/isolinux/'
ver = bdisk['ver'] ver = bdisk['ver']
if len(arch) == 1: if len(arch) == 1:
isofile = '{0}-{1}-{2}-{3}.iso'.format(bdisk['uxname'], bdisk['ver'], build['buildnum'], arch[0]) isofile = '{0}-{1}-{2}-{3}.iso'.format(bdisk['uxname'], bdisk['ver'], build['buildnum'], arch[0])
@ -285,7 +287,7 @@ def genISO(conf):
efi = True efi = True
if os.path.isfile(isopath): if os.path.isfile(isopath):
os.remove(isopath) os.remove(isopath)
if archboot != tempdir + '/' + bdisk['name']: # best to use static concat here... if archboot != prepdir + '/' + bdisk['name']: # best to use static concat here...
if os.path.isdir(builddir): if os.path.isdir(builddir):
shutil.rmtree(builddir, ignore_errors = True) shutil.rmtree(builddir, ignore_errors = True)
shutil.copytree(archboot, builddir) shutil.copytree(archboot, builddir)
@ -348,7 +350,7 @@ def genISO(conf):
'-no-emul-boot', '-no-emul-boot',
'-isohybrid-gpt-basdat', '-isohybrid-gpt-basdat',
'-output', isopath, '-output', isopath,
tempdir] prepdir]
else: else:
# UNTESTED. TODO. # UNTESTED. TODO.
# I think i want to also get rid of: -boot-load-size 4, # I think i want to also get rid of: -boot-load-size 4,
@ -371,7 +373,7 @@ def genISO(conf):
'-no-emul-boot', '-no-emul-boot',
'-isohybrid-gpt-basdat', '-isohybrid-gpt-basdat',
'-output', isopath, '-output', isopath,
tempdir] prepdir]
DEVNULL = open(os.devnull, 'w') DEVNULL = open(os.devnull, 'w')
subprocess.call(cmd, stdout = DEVNULL, stderr = subprocess.STDOUT) subprocess.call(cmd, stdout = DEVNULL, stderr = subprocess.STDOUT)
# Get size of ISO # Get size of ISO
@ -400,5 +402,5 @@ def displayStats(iso):
print('\t\t\t = Location: {0}'.format(iso[i]['file'])) print('\t\t\t = Location: {0}'.format(iso[i]['file']))


def cleanUp(): def cleanUp():
# TODO: clear out all of tempdir? # TODO: clear out all of prepdir?
pass pass

View File

@ -169,7 +169,7 @@ def parseConfig(confs):
datetime.datetime.now(), datetime.datetime.now(),
config_dict['build']['basedir'])) config_dict['build']['basedir']))
# Make dirs if they don't exist # Make dirs if they don't exist
for d in ('archboot', 'isodir', 'mountpt', 'srcdir', 'tempdir'): for d in ('archboot', 'isodir', 'mountpt', 'srcdir', 'prepdir'):
os.makedirs(config_dict['build'][d], exist_ok = True) os.makedirs(config_dict['build'][d], exist_ok = True)
# Make dirs for sync staging if we need to # Make dirs for sync staging if we need to
for x in ('http', 'tftp'): for x in ('http', 'tftp'):

View File

@ -15,7 +15,7 @@ def buildIPXE(conf):
bdisk = conf['bdisk'] bdisk = conf['bdisk']
ipxe = conf['ipxe'] ipxe = conf['ipxe']
mini = ipxe['iso'] mini = ipxe['iso']
tempdir = conf['build']['tempdir'] prepdir = conf['build']['prepdir']
templates_dir = build['basedir'] + '/extra/templates' templates_dir = build['basedir'] + '/extra/templates'
ipxe_tpl = templates_dir + '/iPXE' ipxe_tpl = templates_dir + '/iPXE'
srcdir = build['srcdir'] srcdir = build['srcdir']
@ -102,11 +102,12 @@ def genISO(conf):
bdisk = conf['bdisk'] bdisk = conf['bdisk']
ipxe = conf['ipxe'] ipxe = conf['ipxe']
arch = build['arch'] arch = build['arch']
dlpath = build['dlpath']
ver = bdisk['ver'] ver = bdisk['ver']
isodir = build['isodir'] isodir = build['isodir']
isofile = '{0}-{1}-{2}.mini.iso'.format(bdisk['uxname'], bdisk['ver'], build['buildnum']) isofile = '{0}-{1}-{2}.mini.iso'.format(bdisk['uxname'], bdisk['ver'], build['buildnum'])
isopath = '{0}/{1}'.format(isodir, isofile) isopath = '{0}/{1}'.format(isodir, isofile)
tempdir = build['tempdir'] prepdir = build['prepdir']
chrootdir = build['chrootdir'] chrootdir = build['chrootdir']
mini = ipxe['iso'] mini = ipxe['iso']
iso = {} iso = {}
@ -116,8 +117,8 @@ def genISO(conf):
templates_dir = build['basedir'] + '/extra/templates/iPXE/' templates_dir = build['basedir'] + '/extra/templates/iPXE/'
tpl_loader = jinja2.FileSystemLoader(templates_dir) tpl_loader = jinja2.FileSystemLoader(templates_dir)
env = jinja2.Environment(loader = tpl_loader) env = jinja2.Environment(loader = tpl_loader)
bootdir = tempdir + '/ipxe_mini' bootdir = '{0}/ipxe_mini'.format(dlpath)
efiboot_img = bootdir + '/EFI/BOOT/mini.efi' efiboot_img = '{0}/EFI/BOOT/mini.efi'.format(bootdir)
innerefi64 = '{0}/src/bin-x86_64-efi/ipxe.efi'.format(ipxe_src) innerefi64 = '{0}/src/bin-x86_64-efi/ipxe.efi'.format(ipxe_src)
efi = False efi = False
# this shouldn't be necessary... if it is, we can revisit this in the future. see "Inner dir" below. # this shouldn't be necessary... if it is, we can revisit this in the future. see "Inner dir" below.
@ -130,7 +131,7 @@ def genISO(conf):
if os.path.isdir(bootdir): if os.path.isdir(bootdir):
shutil.rmtree(bootdir) shutil.rmtree(bootdir)
os.makedirs('{0}/EFI/BOOT'.format(bootdir), exist_ok = True) # EFI os.makedirs('{0}/EFI/BOOT'.format(bootdir), exist_ok = True) # EFI
# Inner dir (efiboot.efi file) # Inner dir (mini.efi file)
sizetotal = 65536 # 64K wiggle room. increase this if we add IA64. sizetotal = 65536 # 64K wiggle room. increase this if we add IA64.
sizetotal += os.path.getsize(innerefi64) sizetotal += os.path.getsize(innerefi64)
print("{0}: [IPXE] Creating EFI ESP image {1} ({2})...".format( print("{0}: [IPXE] Creating EFI ESP image {1} ({2})...".format(
@ -155,7 +156,7 @@ def genISO(conf):
os.makedirs('{0}/loader/entries'.format(bootdir), exist_ok = True) # EFI os.makedirs('{0}/loader/entries'.format(bootdir), exist_ok = True) # EFI
os.makedirs('{0}/isolinux'.format(bootdir), exist_ok = True) # BIOS os.makedirs('{0}/isolinux'.format(bootdir), exist_ok = True) # BIOS
# we reuse the preloader.efi from full ISO build # we reuse the preloader.efi from full ISO build
shutil.copy2('{0}/EFI/boot/bootx64.efi'.format(tempdir), shutil.copy2('{0}/EFI/boot/bootx64.efi'.format(prepdir),
'{0}/EFI/BOOT/BOOTX64.EFI'.format(bootdir)) '{0}/EFI/BOOT/BOOTX64.EFI'.format(bootdir))
# and we create the loader entries # and we create the loader entries
for t in ('loader','base'): for t in ('loader','base'):
@ -197,7 +198,7 @@ def genISO(conf):
'-boot-info-table', '-boot-info-table',
'-isohybrid-mbr', '{0}/root.{1}/usr/lib/syslinux/bios/isohdpfx.bin'.format(chrootdir, arch[0]), '-isohybrid-mbr', '{0}/root.{1}/usr/lib/syslinux/bios/isohdpfx.bin'.format(chrootdir, arch[0]),
'-eltorito-alt-boot', '-eltorito-alt-boot',
'-e', 'efiboot.efi', '-e', 'EFI/BOOT/mini.efi',
'-no-emul-boot', '-no-emul-boot',
'-isohybrid-gpt-basdat', '-isohybrid-gpt-basdat',
'-output', isopath, '-output', isopath,

View File

@ -2,7 +2,6 @@ import os
import shutil import shutil
import re import re
import hashlib import hashlib
import gpgme
import tarfile import tarfile
import subprocess import subprocess
import re import re
@ -11,18 +10,20 @@ import datetime
import humanize import humanize
from urllib.request import urlopen from urllib.request import urlopen
import host # bdisk.host import host # bdisk.host
import bGPG # bdisk.bGPG




def dirChk(config_dict): def dirChk(config_dict):
# Make dirs if they don't exist # Make dirs if they don't exist
for d in ('archboot', 'isodir', 'mountpt', 'srcdir', 'tempdir'): for d in ('archboot', 'isodir', 'mountpt', 'srcdir', 'prepdir'):
os.makedirs(config_dict['build'][d], exist_ok = True) os.makedirs(config_dict['build'][d], exist_ok = True)
# Make dirs for sync staging if we need to # Make dirs for sync staging if we need to
for x in ('http', 'tftp'): for x in ('http', 'tftp'):
if config_dict['sync'][x]: if config_dict['sync'][x]:
os.makedirs(config_dict[x]['path'], exist_ok = True) os.makedirs(config_dict[x]['path'], exist_ok = True)


def downloadTarball(build): def downloadTarball(conf):
build = conf['build']
dlpath = build['dlpath'] dlpath = build['dlpath']
arch = build['arch'] arch = build['arch']
#mirror = 'http://mirrors.kernel.org/archlinux' #mirror = 'http://mirrors.kernel.org/archlinux'
@ -41,9 +42,6 @@ def downloadTarball(build):
sha_list = list(filter(None, sha_raw.split('\n'))) sha_list = list(filter(None, sha_raw.split('\n')))
sha_dict = {x.split()[1]: x.split()[0] for x in sha_list} sha_dict = {x.split()[1]: x.split()[0] for x in sha_list}
# all that lousy work just to get a sha1 sum. okay. so. # all that lousy work just to get a sha1 sum. okay. so.
if build['mirrorgpgsig'] != '':
# we don't want to futz with the user's normal gpg.
gpg = gnupg.GPG(gnupghome = dlpath + '/.gnupg')
for a in arch: for a in arch:
pattern = re.compile('^.*' + a + '\.tar(\.(gz|bz2|xz))?$') pattern = re.compile('^.*' + a + '\.tar(\.(gz|bz2|xz))?$')
tarball = [filename.group(0) for l in list(sha_dict.keys()) for filename in [pattern.search(l)] if filename][0] tarball = [filename.group(0) for l in list(sha_dict.keys()) for filename in [pattern.search(l)] if filename][0]
@ -114,12 +112,15 @@ def unpackTarball(tarball_path, build, keep = False):
tar.close() tar.close()
print("{0}: [PREP] Extraction for {1} finished.".format(datetime.datetime.now(), tarball_path[a])) print("{0}: [PREP] Extraction for {1} finished.".format(datetime.datetime.now(), tarball_path[a]))


def buildChroot(build, keep = False): def buildChroot(conf, keep = False):
build = conf['build']
bdisk = conf['bdisk']
user = conf['user']
dlpath = build['dlpath'] dlpath = build['dlpath']
chrootdir = build['chrootdir'] chrootdir = build['chrootdir']
arch = build['arch'] arch = build['arch']
extradir = build['basedir'] + '/extra' extradir = build['basedir'] + '/extra'
unpack_me = unpackTarball(downloadTarball(build), build, keep) unpack_me = unpackTarball(downloadTarball(conf), build, keep)
# build dict of lists of files and dirs from pre-build.d dir, do the same with arch-specific changes. # build dict of lists of files and dirs from pre-build.d dir, do the same with arch-specific changes.
prebuild_overlay = {} prebuild_overlay = {}
prebuild_arch_overlay = {} prebuild_arch_overlay = {}
@ -158,9 +159,12 @@ def buildChroot(build, keep = False):
shutil.copy2(extradir + '/pre-build.d/' + a + '/' + file, chrootdir + '/root.' + a + '/' + file, follow_symlinks = False) shutil.copy2(extradir + '/pre-build.d/' + a + '/' + file, chrootdir + '/root.' + a + '/' + file, follow_symlinks = False)
os.chown(chrootdir + '/root.' + a + '/' + file, 0, 0, follow_symlinks = False) os.chown(chrootdir + '/root.' + a + '/' + file, 0, 0, follow_symlinks = False)


def prepChroot(build, bdisk, user): def prepChroot(conf):
build = conf['build']
bdisk = conf['bdisk']
user = conf['user']
chrootdir = build['chrootdir'] chrootdir = build['chrootdir']
tempdir = build['tempdir'] prepdir = build['prepdir']
arch = build['arch'] arch = build['arch']
bdisk_repo_dir = build['basedir'] bdisk_repo_dir = build['basedir']
dlpath = build['dlpath'] dlpath = build['dlpath']
@ -185,7 +189,7 @@ def prepChroot(build, bdisk, user):
for a in arch: for a in arch:
with open('{0}/root.{1}/root/VERSION_INFO.txt'.format(chrootdir, a), 'w+') as f: with open('{0}/root.{1}/root/VERSION_INFO.txt'.format(chrootdir, a), 'w+') as f:
f.write(tpl_out) f.write(tpl_out)
with open(tempdir + '/VERSION_INFO.txt', 'w+') as f: with open(prepdir + '/VERSION_INFO.txt', 'w+') as f:
f.write(tpl_out) f.write(tpl_out)
tpl = env.get_template('VARS.txt.j2') tpl = env.get_template('VARS.txt.j2')
tpl_out = tpl.render(bdisk = bdisk, user = user) tpl_out = tpl.render(bdisk = bdisk, user = user)

51
docs/manual/00-HEAD.adoc Normal file
View File

@ -0,0 +1,51 @@
BDisk User and Developer Manual
===============================
Brent Saner
v1.0, 2016-12
:doctype: book

<<<

[dedication]
Thanks
======
See CREDITS in the project source for a list of thanks.

[preface]
Preface
=======
I (Brent Saner) am a GNU/Linux Systems/Network Administrator/Engineer- I wear a lot of hats. I have a lot of side projects to keep me busy when Im not working at _${dayjob}_, mostly to assist in other side projects and become more efficient and proficient at those tasks. “Shaving the yak,” footnote:[See http://catb.org/jargon/html/Y/yak-shaving.html] indeed.

A lot of research went into how low-level boot operations take place when writing BDisk, both in BIOS and UEFI footnote:[*Unified Extensible Firmware Interface.* UEFI is not BIOS, and BIOS is not UEFI.] (and corresponding concepts such as Secureboot, etc.) which is no easy task to understand and very commonly misunderstood. (For instance, a common misconception is that UEFI necessarily implies Secureboot. This is quite far from the truth and UEFI by itself is quite a useful replacement for BIOS). I invite you to do research into the specifications yourself; it's rather fascinating.

What is BDisk?
~~~~~~~~~~~~~~
BDisk refers to both a live distribution I use in my own uses (for rescue situations, recovery, etc.) but foremost and most importantly, it also refers to the tool I use for building that distribution. The latter is what this project and documentation refer to when the word “BDisk” is used.

When I rewrote BDisk in Python 3.x (I should take the time to note that I am still quite new to Python so expect there to be plenty of optimizations to be made and general WTF-ery from seasoned Python developers), one of my main goals was to make it as easy to use as possible. This is surprisingly hard to do- its quite challenging to try to approach software youve written with the mindset of someone other than you.

Its my hope that by releasing this utility (and documenting it), you can use it and save some time for yourself as well (and hopefully get the chance to learn a bit more in the process!).

Copyright/Licensing
~~~~~~~~~~~~~~~~~~~
BDisk is GPLv3-licensed. This means that you can use it for business reasons, personal reasons, modify it, etc. Please be sure to familiarize yourself with the full set of terms. You can find the full license in `docs/LICENSE`.

image::https://www.gnu.org/graphics/gplv3-127x51.png[GPLv3,align="center"]

This document (and all other associated author-generated documentation) are released under the http://creativecommons.org/licenses/by-sa/4.0/[Creative Commons CC-BY-SA 4.0] copyright.

image::https://i.creativecommons.org/l/by-sa/4.0/88x31.png[CC-BY-SA_4.0,align="center"]

<<<

User Manual
===========

[partintro]
.What good is software if nobody uses it?
--
BDisk was ultimately designed to make your life easier.
--

TEXT
----

View File

@ -199,6 +199,8 @@ gpgkey = 7F2D434B9741E8AC
; If you don't specify a personal GPG config ; If you don't specify a personal GPG config
; (under the gpg section), then you'll definitely probably ; (under the gpg section), then you'll definitely probably
; want to leave this blank. ; want to leave this blank.
; 2.) If set, make sure you use a valid URI (e.g.:
; hkp://pgp.mit.edu )
gpgkeyserver = gpgkeyserver =


; Should we sign our release files? (See the GPG section) ; Should we sign our release files? (See the GPG section)
@ -242,14 +244,14 @@ srcdir = ${dlpath}/src
; What directory should we use for staging? ; What directory should we use for staging?
; 0.) No whitespace ; 0.) No whitespace
; 1.) Will be created if it doesn't exist ; 1.) Will be created if it doesn't exist
tempdir = ${dlpath}/temp prepdir = ${dlpath}/temp


; Where should we stage the boot files? ; Where should we stage the boot files?
; This should not be the same dir as other options! ; This should not be the same dir as other options!
; The default is recommended. ; The default is recommended.
; 0.) No whitespace ; 0.) No whitespace
; 1.) Will be created if it doesn't exist ; 1.) Will be created if it doesn't exist
archboot = ${tempdir}/${bdisk:name} archboot = ${prepdir}/${bdisk:name}


; What directory/path should we use as a base ; What directory/path should we use as a base
; directory for mountpoints? ; directory for mountpoints?

View File

@ -1,6 +1,9 @@
#!/bin/bash #!/bin/bash


source /etc/bash.bashrc source /etc/bash.bashrc
# needed so we override whatever's set in python
# alternatively, we can just mkdir -p $GNUPGHOME
export GNUPGHOME=/root/.gnupg


# Import settings. # Import settings.
source /root/VARS.txt source /root/VARS.txt

View File

@ -1,3 +1,3 @@
title {{ bdisk['pname'] }} iPXE (netboot) title {{ bdisk['pname'] }} iPXE (netboot)
efi /EFI/BOOT/efiboot.efi efi /EFI/BOOT/mini.efi