#!/usr/bin/env python3 ## REQUIRES: ## # parted # # sgdisk ### (yes, both) # python 3 with standard library # (OPTIONAL) lxml # pacman in the host environment # arch-install-scripts: https://www.archlinux.org/packages/extra/any/arch-install-scripts/ # a network connection # the proper kernel arguments. try: from lxml import etree lxml_avail = True except ImportError: import xml.etree.ElementTree as etree # https://docs.python.org/3/library/xml.etree.elementtree.html lxml_avail = False import shlex import os import re import subprocess import pprint import urllib.request as urlrequest import urllib.parse as urlparse import urllib.response as urlresponse from ftplib import FTP_TLS from io import StringIO class aif(object): def __init__(self): pass def kernelargs(self): if 'DEBUG' in os.environ.keys(): kernelparamsfile = '/tmp/cmdline' else: kernelparamsfile = '/proc/cmdline' args = {} args['aif'] = False # For FTP or HTTP auth args['aif_user'] = False args['aif_password'] = False args['aif_auth'] = False args['aif_realm'] = False args['aif_auth'] = 'basic' with open(kernelparamsfile, 'r') as f: cmdline = f.read() for p in shlex.split(cmdline): if p.startswith('aif'): param = p.split('=') if len(param) == 1: param.append(True) args[param[0]] = param[1] if not args['aif']: exit('You do not have AIF enabled. Exiting.') args['aif_auth'] = args['aif_auth'].lower() return(args) def getConfig(self, args = False): if not args: args = self.kernelargs() # Sanitize the user specification and find which protocol to use prefix = args['aif_url'].split(':')[0].lower() # Use the urllib module if prefix in ('http', 'https', 'file', 'ftp'): if args['aif_user'] and args['aif_password']: # Set up Basic or Digest auth. passman = urlrequest.HTTPPasswordMgrWithDefaultRealm() if not args['aif_realm']: passman.add_password(None, args['aif_url'], args['aif_user'], args['aif_password']) else: passman.add_password(args['aif_realm'], args['aif_url'], args['aif_user'], args['aif_password']) if args['aif_auth'] == 'digest': httpauth = urlrequest.HTTPDigestAuthHandler(passman) else: httpauth = urlrequest.HTTPBasicAuthHandler(passman) httpopener = urlrequest.build_opener(httpauth) urlrequest.install_opener(httpopener) with urlrequest.urlopen(args['aif_url']) as f: conf = f.read() elif prefix == 'ftps': if args['aif_user']: username = args['aif_user'] else: username = 'anonymous' if args['aif_password']: password = args['aif_password'] else: password = 'anonymous' filepath = '/'.join(args['aif_url'].split('/')[3:]) server = args['aif_url'].split('/')[2] content = StringIO() ftps = FTP_TLS(server) ftps.login(username, password) ftps.prot_p() ftps.retrlines("RETR " + filepath, content.write) conf = content.getvalue() else: exit('{0} is not a recognised URI type specifier. Must be one of http, https, file, ftp, or ftps.'.format(prefix)) return(conf) def getXML(self, confobj = False): if not confobj: confobj = self.getConfig() xmlobj = etree.fromstring(confobj) return(xmlobj) def buildDict(self, xmlobj = False): if not xmlobj: xmlobj = self.getXML() # Set up the skeleton dicts aifdict = {} for i in ('disk', 'mount', 'network', 'system', 'users', 'software', 'scripts'): aifdict[i] = {} for i in ('network.ifaces', 'system.bootloader', 'system.services', 'users.root', 'scripts.pre', 'scripts.post'): i = i.split('.') dictname = i[0] keyname = i[1] aifdict[dictname][keyname] = {} aifdict['users']['root']['password'] = False for i in ('repos', 'mirrors', 'packages'): aifdict['software'][i] = {} # Set up the dict elements for disk partitioning for i in xmlobj.findall('storage/disk'): disk = i.attrib['device'] fmt = i.attrib['diskfmt'].lower() if not fmt in ('gpt', 'bios'): exit('Device {0}\'s format "{1}" is not a valid type (one of gpt, bios).'.format(disk, fmt)) aifdict['disk'][disk] = {} aifdict['disk'][disk]['fmt'] = fmt aifdict['disk'][disk]['parts'] = {} for x in i: if x.tag == 'part': partnum = x.attrib['num'] aifdict['disk'][disk]['parts'][partnum] = {} for a in x.attrib: aifdict['disk'][disk]['parts'][partnum][a] = x.attrib[a] # Set up mountpoint dicts for i in xmlobj.findall('storage/mount'): device = i.attrib['source'] mntpt = i.attrib['target'] order = i.attrib['order'] if 'fstype' in i.keys(): fstype = i.attrib['fstype'] else: fstype = None if 'opts' in i.keys(): opts = i.attrib['opts'] else: opts = None aifdict['mount'][order] = {} aifdict['mount'][order]['device'] = device aifdict['mount'][order]['mountpt'] = mntpt aifdict['mount'][order]['fstype'] = fstype aifdict['mount'][order]['opts'] = opts # Set up networking dicts aifdict['network']['hostname'] = xmlobj.find('network').attrib['hostname'] for i in xmlobj.findall('network/iface'): # Create a dict for the iface name. iface = i.attrib['device'] proto = i.attrib['netproto'] address = i.attrib['address'] if iface not in aifdict['network']['ifaces'].keys(): aifdict['network']['ifaces'][iface] = {} if proto not in aifdict['network']['ifaces'][iface].keys(): aifdict['network']['ifaces'][iface][proto] = [] aifdict['network']['ifaces'][iface][proto].append(address) # Set up the users dicts aifdict['users']['root']['password'] = xmlobj.find('system/users').attrib['rootpass'] for i in xmlobj.findall('system/users'): for x in i: username = x.attrib['name'] aifdict['users'][username] = {} for a in ('uid', 'group', 'gid', 'password', 'comment', 'sudo'): if a in x.attrib.keys(): aifdict['users'][username][a] = x.attrib[a] else: aifdict['users'][username][a] = None sudo = (x.attrib['sudo']).lower() in ('true', '1') aifdict['users'][username]['sudo'] = sudo # And we also need to handle the homedir and xgroup situation for n in ('home', 'xgroup'): aifdict['users'][username][n] = False for a in x: if not aifdict['users'][username][a.tag]: aifdict['users'][username][a.tag] = {} for b in a.attrib: if a.tag == 'xgroup': if b == 'name': groupname = a.attrib[b] if groupname not in aifdict['users'][username]['xgroup'].keys(): aifdict['users'][username]['xgroup'][a.attrib[b]] = {} else: aifdict['users'][username]['xgroup'][a.attrib['name']][b] = a.attrib[b] else: aifdict['users'][username][a.tag][b] = a.attrib[b] # And fill in any missing values. We could probably use the XSD and use of defaults to do this, but... oh well. if isinstance(aifdict['users'][username]['xgroup'], dict): for g in aifdict['users'][username]['xgroup'].keys(): for k in ('create', 'gid'): if k not in aifdict['users'][username]['xgroup'][g].keys(): aifdict['users'][username]['xgroup'][g][k] = False elif k == 'create': aifdict['users'][username]['xgroup'][g][k] = aifdict['users'][username]['xgroup'][g][k].lower() in ('true', '1') if isinstance(aifdict['users'][username]['home'], dict): for k in ('path', 'create'): if k not in aifdict['users'][username]['home'].keys(): aifdict['users'][username]['home'][k] = False elif k == 'create': aifdict['users'][username]['home'][k] = aifdict['users'][username]['home'][k].lower() in ('true', '1') # Set up the system settings, if applicable. aifdict['system']['timezone'] = False aifdict['system']['locale'] = False aifdict['system']['kbd'] = False aifdict['system']['chrootpath'] = False for i in ('locale', 'timezone', 'kbd', 'chrootpath'): if i in xmlobj.find('system').attrib: aifdict['system'][i] = xmlobj.find('system').attrib[i] # And now services... if xmlobj.find('system/service') is None: aifdict['system']['services'] = False else: for x in xmlobj.findall('system/service'): svcname = x.attrib['name'] state = x.attrib['status'].lower() in ('true', '1') aifdict['system']['services'][svcname] = {} aifdict['system']['services'][svcname]['status'] = state # And software. First the mirror list. if xmlobj.find('pacman/mirrorlist') is None: aifdict['software']['mirrors'] = False else: aifdict['software']['mirrors'] = [] for x in xmlobj.findall('pacman/mirrorlist'): for i in x: aifdict['software']['mirrors'].append(i.text) # And then the repo list. for x in xmlobj.find('pacman/repos'): repo = x.attrib['name'] aifdict['software']['repos'][repo] = {} aifdict['software']['repos'][repo]['enabled'] = x.attrib['enabled'].lower() in ('true', '1') aifdict['software']['repos'][repo]['siglevel'] = x.attrib['siglevel'] aifdict['software']['repos'][repo]['mirror'] = x.attrib['mirror'] # And packages. if xmlobj.find('pacman/software') is None: aifdict['software']['packages'] = False else: aifdict['software']['packages'] = {} for x in xmlobj.findall('pacman/software/package'): aifdict['software']['packages'][x.attrib['name']] = {} if 'repo' in x.attrib: aifdict['software']['packages'][x.attrib['name']]['repo'] = x.attrib['repo'] else: aifdict['software']['packages'][x.attrib['name']]['repo'] = None # The bootloader setup... for x in xmlobj.find('bootloader').attrib: aifdict['system']['bootloader'][x] = xmlobj.find('bootloader').attrib[x] return(aifdict) class archInstall(object): def __init__(self, aifdict): for k, v in aifdict.items(): setattr(self, k, v) def format(self): # NOTE: the following is a dict of fstype codes to their description. fstypes = {'0700': 'Microsoft basic data', '0c01': 'Microsoft reserved', '2700': 'Windows RE', '3000': 'ONIE config', '3900': 'Plan 9', '4100': 'PowerPC PReP boot', '4200': 'Windows LDM data', '4201': 'Windows LDM metadata', '4202': 'Windows Storage Spaces', '7501': 'IBM GPFS', '7f00': 'ChromeOS kernel', '7f01': 'ChromeOS root', '7f02': 'ChromeOS reserved', '8200': 'Linux swap', '8300': 'Linux filesystem', '8301': 'Linux reserved', '8302': 'Linux /home', '8303': 'Linux x86 root (/)', '8304': 'Linux x86-64 root (/', '8305': 'Linux ARM64 root (/)', '8306': 'Linux /srv', '8307': 'Linux ARM32 root (/)', '8400': 'Intel Rapid Start', '8e00': 'Linux LVM', 'a500': 'FreeBSD disklabel', 'a501': 'FreeBSD boot', 'a502': 'FreeBSD swap', 'a503': 'FreeBSD UFS', 'a504': 'FreeBSD ZFS', 'a505': 'FreeBSD Vinum/RAID', 'a580': 'Midnight BSD data', 'a581': 'Midnight BSD boot', 'a582': 'Midnight BSD swap', 'a583': 'Midnight BSD UFS', 'a584': 'Midnight BSD ZFS', 'a585': 'Midnight BSD Vinum', 'a600': 'OpenBSD disklabel', 'a800': 'Apple UFS', 'a901': 'NetBSD swap', 'a902': 'NetBSD FFS', 'a903': 'NetBSD LFS', 'a904': 'NetBSD concatenated', 'a905': 'NetBSD encrypted', 'a906': 'NetBSD RAID', 'ab00': 'Recovery HD', 'af00': 'Apple HFS/HFS+', 'af01': 'Apple RAID', 'af02': 'Apple RAID offline', 'af03': 'Apple label', 'af04': 'AppleTV recovery', 'af05': 'Apple Core Storage', 'bc00': 'Acronis Secure Zone', 'be00': 'Solaris boot', 'bf00': 'Solaris root', 'bf01': 'Solaris /usr & Mac ZFS', 'bf02': 'Solaris swap', 'bf03': 'Solaris backup', 'bf04': 'Solaris /var', 'bf05': 'Solaris /home', 'bf06': 'Solaris alternate sector', 'bf07': 'Solaris Reserved 1', 'bf08': 'Solaris Reserved 2', 'bf09': 'Solaris Reserved 3', 'bf0a': 'Solaris Reserved 4', 'bf0b': 'Solaris Reserved 5', 'c001': 'HP-UX data', 'c002': 'HP-UX service', 'ea00': 'Freedesktop $BOOT', 'eb00': 'Haiku BFS', 'ed00': 'Sony system partition', 'ed01': 'Lenovo system partition', 'ef00': 'EFI System', 'ef01': 'MBR partition scheme', 'ef02': 'BIOS boot partition', 'f800': 'Ceph OSD', 'f801': 'Ceph dm-crypt OSD', 'f802': 'Ceph journal', 'f803': 'Ceph dm-crypt journal', 'f804': 'Ceph disk in creation', 'f805': 'Ceph dm-crypt disk in creation', 'fb00': 'VMWare VMFS', 'fb01': 'VMWare reserved', 'fc00': 'VMWare kcore crash protection', 'fd00': 'Linux RAID'} # We want to build a mapping of commands to run after partitioning. This will be fleshed out in the future to hopefully include more. formatting = {} # TODO: we might want to provide a way to let users specify extra options here. # TODO: label support? formatting['ef00'] = ['mkfs.vfat', '-F', '32', '%PART%'] formatting['ef01'] = formatting['ef00'] formatting['ef02'] = formatting['ef00'] formatting['8200'] = ['mkswap', '-c', '%PART%'] formatting['8300'] = ['mkfs.ext4', '-c', '-q', '%PART%'] # some people are DEFINITELY not going to be happy about this. we need to figure out a better way to customize this. for fs in ('8301', '8302', '8303', '8304', '8305', '8306', '8307'): formatting[fs] = formatting['8300'] #formatting['8e00'] = FOO # TODO: LVM configuration #formatting['fd00'] = FOO # TODO: MDADM configuration cmds = [] for d in self.disk: partnums = [int(x) for x in self.disk[d]['parts'].keys()] partnums.sort() cmds.append(['sgdisk', '-Z', d]) if self.disk[d]['fmt'] == 'gpt': diskfmt = 'gpt' if len(partnums) >= 129 or partnums[-1:] >= 129: exit('GPT only supports 128 partitions (and partition allocations).') cmds.append(['sgdisk', '-og', d]) elif self.disk[d]['fmt'] == 'bios': diskfmt = 'msdos' cmds.append(['sgdisk', '-om', d]) cmds.append(['parted', d, '--script', '-a', 'optimal']) with open(os.devnull, 'w') as DEVNULL: for c in cmds: subprocess.call(c, stdout = DEVNULL, stderr = subprocess.STDOUT) cmds = [] disksize = {} disksize['start'] = subprocess.check_output(['sgdisk', '-F', d]) disksize['max'] = subprocess.check_output(['sgdisk', '-E', d]) for p in partnums: # Need to do some mathz to get the actual sectors if we're using percentages. for s in ('start', 'size'): val = self.disk[d]['parts'][str(p)][s] if '%' in val: stripped = val.replace('%', '') modifier = re.sub('[0-9]+%', '', val) percent = re.sub('(-|\+)*', '', stripped) decimal = float(percent) / 100 newval = int(int(disksize['max']) * decimal) if s == 'start': newval = newval + int(disksize['start']) self.disk[d]['parts'][str(p)][s] = modifier + str(newval) if self.disk[d]['fmt'] == 'gpt': for p in partnums: size = {} size['start'] = self.disk[d]['parts'][str(p)]['start'] size['end'] = self.disk[d]['parts'][str(p)]['size'] fstype = self.disk[d]['parts'][str(p)]['fstype'].lower() if fstype not in fstypes.keys(): print('Filesystem type {0} is not valid. Must be a code from:\nCODE:FILESYSTEM'.format(fstype)) for k, v in fstypes.items(): print(k + ":" + v) exit() cmds.append(['sgdisk', '-n', '{0}:{1}:{2}'.format(str(p), self.disk[d]['parts'][str(p)]['start'], self.disk[d]['parts'][str(p)]['size']), #'-c', '{0}:"{1}"'.format(str(p), self.disk[d]['parts'][str(p)]['label']), # TODO: add support for partition labels '-t', '{0}:{1}'.format(str(p), fstype), d]) mkformat = formatting[fstype] for x, y in enumerate(mkformat): if y == '%PART%': mkformat[x] = d + str(p) cmds.append(mkformat) import pprint pprint.pprint(cmds) with open(os.devnull, 'w') as DEVNULL: for p in cmds: subprocess.call(p, stdout = DEVNULL, stderr = subprocess.STDOUT) def mounts(self): mntorder = list(self.mount.keys()) mntorder.sort() for m in mntorder: mnt = self.mount[m] if mnt['mountpt'].lower() == 'swap': cmd = ['swapon', mnt['device']] else: cmd = ['mount', mnt['device'], mnt['mountpt']] if mnt['opts']: cmd.insert(1, '-o {0}'.format(mnt['opts'])) if mnt['fstype']: cmd.insert(1, '-t {0}'.format(mnt['fstype'])) # with open(os.devnull, 'w') as DEVNULL: # for p in cmd: # subprocess.call(p, stdout = DEVNULL, stderr = subprocess.STDOUT) # And we need to add some extra mounts to support a chroot. We also need to know what was mounted before. with open('/proc/mounts', 'r') as f: procmounts = f.read() mountlist = {} for i in procmounts.splitlines(): mountlist[i.split()[1]] = i cmounts = {} for m in ('chroot', 'resolv', 'proc', 'sys', 'efi', 'dev', 'pts', 'shm', 'run', 'tmp'): cmounts[m] = None chrootdir = self.system['chrootpath'] # chroot (bind mount... onto itself. it's so stupid, i know. see https://bugs.archlinux.org/task/46169) if chrootdir not in mountlist.keys(): cmounts['chroot'] = ['mount', '--bind', chrootdir, chrootdir] # resolv.conf (for DNS resolution in the chroot) if (chrootdir + '/etc/resolv.conf') not in mountlist.keys(): cmounts['resolv'] = ['/bin/mount', '--bind', '-o', 'ro', '/etc/resolv.conf', chrootdir + '/etc/resolv.conf'] # proc if (chrootdir + '/proc') not in mountlist.keys(): cmounts['proc'] = ['/bin/mount', '-t', 'proc', '-o', 'nosuid,noexec,nodev', 'proc', chrootdir + '/proc'] # sys if (chrootdir + '/sys') not in mountlist.keys(): cmounts['sys'] = ['/bin/mount', '-t', 'sysfs', '-o', 'nosuid,noexec,nodev,ro', 'sys', chrootdir + '/sys'] # efi (if it exists on the host) if '/sys/firmware/efi/efivars' in mountlist.keys(): if (chrootdir + '/sys/firmware/efi/efivars') not in mountlist.keys(): cmounts['efi'] = ['/bin/mount', '-t', 'efivarfs', '-o', 'nosuid,noexec,nodev', 'efivarfs', chrootdir + '/sys/firmware/efi/efivars'] # dev if (chrootdir + '/dev') not in mountlist.keys(): cmounts['dev'] = ['/bin/mount', '-t', 'devtmpfs', '-o', 'mode=0755,nosuid', 'udev', chrootdir + '/dev'] # pts if (chrootdir + '/dev/pts') not in mountlist.keys(): cmounts['pts'] = ['/bin/mount', '-t', 'devpts', '-o', 'mode=0620,gid=5,nosuid,noexec', 'devpts', chrootdir + '/dev/pts'] # shm (if it exists on the host) if '/dev/shm' in mountlist.keys(): if (chrootdir + '/dev/shm') not in mountlist.keys(): cmounts['shm'] = ['/bin/mount', '-t', 'tmpfs', '-o', 'mode=1777,nosuid,nodev', 'shm', chrootdir + '/dev/shm'] # run (if it exists on the host) if '/run' in mountlist.keys(): if (chrootdir + '/run') not in mountlist.keys(): cmounts['run'] = ['/bin/mount', '-t', 'tmpfs', '-o', 'nosuid,nodev,mode=0755', 'run', chrootdir + '/run'] # tmp (if it exists on the host) if '/tmp' in mountlist.keys(): if (chrootdir + '/tmp') not in mountlist.keys(): cmounts['tmp'] = ['/bin/mount', '-t', 'tmpfs', '-o', 'mode=1777,strictatime,nodev,nosuid', 'tmp', chrootdir + '/tmp'] # Because the order of these mountpoints is so ridiculously important, we hardcode it. Yeah, python 3.6 has ordered dicts, but do we really want to risk it? with open(os.devnull, 'w') as DEVNULL: for m in ('chroot', 'resolv', 'proc', 'sys', 'efi', 'dev', 'pts', 'shm', 'run', 'tmp'): if cmounts[m]: subprocess.call(cmounts[m], stdout = DEVNULL, stderr = subprocess.STDOUT) # Okay. So we finally have all the mounts bound. Whew. def setup(self): # TODO: could we leverage https://github.com/hartwork/image-bootstrap somehow? I want to keep this close # to standard Python libs, though, to reduce dependency requirements. hostscript = [] chrootcmds = [] locales = [] locale = [] # Get the necessary fstab additions for the guest chrootfstab = subprocess.check_output(['genfstab', '-U', self.system['chrootpath']]) # Set up the time, and then kickstart the guest install. hostscript.append(['timedatectl', 'set-ntp', 'true']) hostscript.append(['pacstrap', self.system['chrootpath'], 'base']) with open('{0}/etc/fstab'.format(self.system['chrootpath']), 'a') as f: f.write(chrootfstab.decode('utf-8')) # Validating this would be better with pytz, but it's not stdlib. dateutil would also work, but same problem. # https://stackoverflow.com/questions/15453917/get-all-available-timezones tzlist = subprocess.check_output(['timedatectl', 'list-timezones']).decode('utf-8').splitlines() if self.system['timezone'] not in tzlist: print('WARNING (non-fatal): {0} does not seem to be a valid timezone, but we\'re continuing anyways.'.format(self.system['timezone'])) os.symlink('/usr/share/zoneinfo/{0}'.format(self.system['timezone']), '{0}/etc/localtime'.format(self.system['chrootpath'])) # This is an ugly hack. TODO: find a better way of determining if the host is set to UTC in the RTC. maybe the datetime module can do it. utccheck = subprocess.check_output(['timedatectl', 'status']).decode('utf-8').splitlines() utccheck = [x.strip(' ') for x in utccheck] for i, v in enumerate(utccheck): if v.startswith('RTC in local'): utcstatus = (v.split(': ')[1]).lower() in ('yes') break if utcstatus: chrootcmds.append(['hwclock', '--systohc']) # We need to check the locale, and set up locale.gen. with open('{0}/etc/locale.gen'.format(self.system['chrootpath']), 'r') as f: localeraw = f.readlines() for line in localeraw: if not line.startswith('# '): # Comments, thankfully, have a space between the leading octothorpe and the comment. Locales have no space. i = line.strip().strip('#') if i != '': # We also don't want blank entries. Keep it clean, folks. locales.append(i) for i in locales: localelst = i.split() if localelst[0].lower().startswith(self.system['locale'].lower()): locale.append(' '.join(localelst).strip()) for i, v in enumerate(localeraw): for x in locale: if v.startswith('#{0}'.format(x)): localeraw[i] = x + '\n' with open('{0}/etc/locale.gen'.format(self.system['chrootpath']), 'w') as f: f.write(''.join(localeraw)) with open('{0}/etc/locale.conf', 'a') as f: f.write('LANG={0}\n'.format(locale[0].split()[0])) chrootcmds.append(['locale-gen']) # Set up the kbd layout. # Currently there is NO validation on this. TODO. if self.system['kbd']: with open('{0}/etc/vconsole.conf'.format(self.system['chrootpath']), 'a') as f: f.write('KEYMAP={0}\n'.format(self.system['kbd'])) # Set up the hostname. with open('{0}/etc/hostname'.format(self.system['chrootpath']), 'w') as f: f.write(self.network['hostname'] + '\n') with open('{0}/etc/hosts'.format(self.system['chrootpath']), 'a') as f: f.write('127.0.0.1\t{0}\t{1}\n'.format(self.network['hostname'], (self.network['hostname']).split('.')[0])) # SET UP NETWORKING HERE ######################## chrootcmds.append(['mkinitcpio', '-p', 'linux']) with open(os.devnull, 'w') as DEVNULL: for c in hostscript: subprocess.call(c, stdout = DEVNULL, stderr = subprocess.STDOUT) return(chrootcmds) def chroot(self, chrootcmds = False): if not chrootcmds: chrootcmds = self.setup() chrootscript = """#!/bin/bash # https://aif.square-r00t.net/ """ with open('{0}/root/aif.sh'.format(self.system['chrootpath']), 'w') as f: f.write(chrootscript) os.chmod('{0}/root/aif.sh'.format(self.system['chrootpath']), 0o700) real_root = os.open("/", os.O_RDONLY) os.chroot(self.system['chrootpath']) # Does this even work with an os.chroot()? Let's hope so! with open(os.devnull, 'w') as DEVNULL: for c in chrootcmds: subprocess.call(c, stdout = DEVNULL, stderr = subprocess.STDOUT) os.system('{0}/root/aif.sh'.format(self.system['chrootpath'])) os.system('{0}/root/aif-post.sh'.format(self.system['chrootpath'])) os.fchdir(real_root) os.chroot('.') os.close(real_root) if not os.path.isfile('{0}/sbin/init'.format(chrootdir)): os.symlink('../lib/systemd/systemd', '{0}/sbin/init'.format(chrootdir)) def unmount(self): with open(os.devnull, 'w') as DEVNULL: subprocess.call(['unmount', '-lR', self.system['chrootpath']], stdout = DEVNULL, stderr = subprocess.STDOUT) def runInstall(confdict): install = archInstall(confdict) #install.format() #install.mounts() ##chrootcmds = install.setup() ##install.chroot(chrootcmds) #install.chroot() #install.unmount() def main(): if os.getuid() != 0: exit('This must be run as root.') conf = aif() import pprint instconf = conf.buildDict() pprint.pprint(instconf) runInstall(instconf) if __name__ == "__main__": main()