client's coming along well, and have a working expect script to automate testing. next up, actual XML generation and writing it out

This commit is contained in:
brent s 2017-05-14 12:07:39 -04:00
parent 4172111fcd
commit d6f4548b88
3 changed files with 91 additions and 70 deletions

View File

@ -132,16 +132,16 @@ class aifgen(object):
ifaces = {} ifaces = {}
moreIfaces = True moreIfaces = True
print('\nPlease enter the name of the interface you would like to use.\n' + print('\nPlease enter the name of the interface you would like to use.\n' +
'Can instead be \'auto\' for automatic configuration of the first found interface\n' + '\tCan instead be \'auto\' for automatic configuration of the first found interface\n' +
'with an active link. (You can only specify one auto device per system, and all subsequent\n' '\twith an active link. (You can only specify one auto device per system, and all subsequent\n'
'interface entries will be ignored.)\n') '\tinterface entries will be ignored.)\n')
while moreIfaces: while moreIfaces:
ifacein = chkPrompt('Interface device: ', nethelp) ifacein = chkPrompt('* Interface device: ', nethelp)
addrin = chkPrompt(('* Address for {0} in CIDR format (can be an IPv4 or IPv6 address; ' + addrin = chkPrompt(('** Address for {0} in CIDR format (can be an IPv4 or IPv6 address; ' +
'use \'auto\' for DHCP/DHCPv6): ').format(ifacein), nethelp) 'use \'auto\' for DHCP/DHCPv6): ').format(ifacein), nethelp)
if addrin == 'auto': if addrin == 'auto':
addrtype = 'auto' addrtype = 'auto'
ipver = (chkPrompt('* Would you like \'ipv4\', \'ipv6\', or \'both\' to be auto-configured? ', nethelp)).lower() ipver = (chkPrompt('** Would you like \'ipv4\', \'ipv6\', or \'both\' to be auto-configured? ', nethelp)).lower()
if ipver not in ('ipv4', 'ipv6', 'both'): if ipver not in ('ipv4', 'ipv6', 'both'):
exit(' !! ERROR: Must be one of ipv4, ipv6, or both.') exit(' !! ERROR: Must be one of ipv4, ipv6, or both.')
else: else:
@ -156,13 +156,13 @@ class aifgen(object):
except ValueError: except ValueError:
exit(' !! ERROR: You did not enter a valid IPv4/IPv6 address.') exit(' !! ERROR: You did not enter a valid IPv4/IPv6 address.')
if addrtype == 'static': if addrtype == 'static':
gwin = chkPrompt('* What is the gateway address for {0}? '.format(addrin), nethelp) gwin = chkPrompt('*** What is the gateway address for {0}? '.format(addrin), nethelp)
try: try:
ipaddress.ip_address(gwin) ipaddress.ip_address(gwin)
except: except:
exit(' !! ERROR: You did not enter a valid IPv4/IPv6 address.') exit(' !! ERROR: You did not enter a valid IPv4/IPv6 address.')
ifaces[ifacein] = {'address': addrin, 'proto': ipver, 'gw': gwin, 'resolvers': []} ifaces[ifacein] = {'address': addrin, 'proto': ipver, 'gw': gwin, 'resolvers': []}
resolversin = chkPrompt('* What DNS resolvers should we use? Can accept a comma-separated list: ', nethelp) resolversin = chkPrompt('*** What DNS resolvers should we use? Can accept a comma-separated list: ', nethelp)
for rslv in resolversin.split(','): for rslv in resolversin.split(','):
rslvaddr = rslv.strip() rslvaddr = rslv.strip()
ifaces[ifacein]['resolvers'].append(rslvaddr) ifaces[ifacein]['resolvers'].append(rslvaddr)
@ -172,14 +172,14 @@ class aifgen(object):
exit(' !! ERROR: {0} is not a valid resolver address.'.format(rslvaddr)) exit(' !! ERROR: {0} is not a valid resolver address.'.format(rslvaddr))
else: else:
ifaces[ifacein] = {'address': 'auto', 'proto': ipver, 'gw': False, 'resolvers': False} ifaces[ifacein] = {'address': 'auto', 'proto': ipver, 'gw': False, 'resolvers': False}
moreIfacesin = input('Would you like to add more interfaces? (y/{0}n{1}) '.format(color.BOLD, color.END)) moreIfacesin = input('* Would you like to add more interfaces? (y/{0}n{1}) '.format(color.BOLD, color.END))
if not re.match('^y(es)?$', moreIfacesin.lower()): if not re.match('^y(es)?$', moreIfacesin.lower()):
moreIfaces = False moreIfaces = False
return(ifaces) return(ifaces)
def genPassHash(user): def genPassHash(user):
# https://bugs.python.org/issue30360 - keep this disabled until we're ready for primetime. # https://bugs.python.org/issue30360 - keep this disabled until we're ready for primetime.
#passin = getpass.getpass('* Please enter the password you want to use for {0} (will not echo back): '.format(user)) passin = getpass.getpass('* Please enter the password you want to use for {0} (will not echo back): '.format(user))
passin = input('* Please enter the password you want to use for {0}: '.format(user)) #passin = input('* Please enter the password you want to use for {0}: '.format(user))
if passin not in ('', '!'): if passin not in ('', '!'):
salt = crypt.mksalt(crypt.METHOD_SHA512) salt = crypt.mksalt(crypt.METHOD_SHA512)
salthash = crypt.crypt(passin, salt) salthash = crypt.crypt(passin, salt)
@ -190,21 +190,21 @@ class aifgen(object):
users = {} users = {}
moreusers = True moreusers = True
while moreusers: while moreusers:
user = chkPrompt('What username would you like to add? ', syshelp) user = chkPrompt('* What username would you like to add? ', syshelp)
if len(user) > 32: if len(user) > 32:
exit(' !! ERROR: Usernames must be less than 32 characters.') exit(' !! ERROR: Usernames must be less than 32 characters.')
if not re.match('^[a-z_][a-z0-9_-]*[$]?$', user): if not re.match('^[a-z_][a-z0-9_-]*[$]?$', user):
exit(' !! ERROR: Your username does not match a valid pattern. See the man page for useradd (\'CAVEATS\').') exit(' !! ERROR: Your username does not match a valid pattern. See the man page for useradd (\'CAVEATS\').')
users[user] = {} users[user] = {}
sudoin = chkPrompt('* Should {0} have (full!) sudo access? (y/{1}n{2}) '.format(user, color.BOLD, color.END), syshelp) sudoin = chkPrompt('** Should {0} have (full!) sudo access? (y/{1}n{2}) '.format(user, color.BOLD, color.END), syshelp)
if re.match('^y(es)?$', sudoin.lower()): if re.match('^y(es)?$', sudoin.lower()):
users[user]['sudo'] = True users[user]['sudo'] = True
else: else:
users[user]['sudo'] = False users[user]['sudo'] = False
users[user]['password'] = genPassHash(user) users[user]['password'] = genPassHash(user)
users[user]['comment'] = chkPrompt(('* What comment should {0} have? ' + users[user]['comment'] = chkPrompt(('** What comment should {0} have? ' +
'(Typically this is the user\'s full name) ').format(user), syshelp) '(Typically this is the user\'s full name) ').format(user), syshelp)
uidin = chkPrompt(('* What UID should {0} have? Leave this blank if you don\'t care ' + uidin = chkPrompt(('** What UID should {0} have? Leave this blank if you don\'t care ' +
'(should be fine for most cases): ').format(user), syshelp) '(should be fine for most cases): ').format(user), syshelp)
if uidin != '': if uidin != '':
try: try:
@ -213,7 +213,7 @@ class aifgen(object):
exit(' !! ERROR: The UID must be an integer.') exit(' !! ERROR: The UID must be an integer.')
else: else:
users[user]['uid'] = False users[user]['uid'] = False
grpin = chkPrompt(('* What group name would you like to use for {0}\'s primary group? ' + grpin = chkPrompt(('** What group name would you like to use for {0}\'s primary group? ' +
'(You\'ll be able to add additional groups in a moment.)\n' + '(You\'ll be able to add additional groups in a moment.)\n' +
'\tThe default, if left blank, is to simply create a group named {0} ' + '\tThe default, if left blank, is to simply create a group named {0} ' +
'(which is what you probably want): ').format(user), syshelp) '(which is what you probably want): ').format(user), syshelp)
@ -226,7 +226,7 @@ class aifgen(object):
else: else:
users[user]['group'] = False users[user]['group'] = False
if grpin != '': if grpin != '':
gidin = chkPrompt(('* What GID should {0} have? Leave this blank if you don\'t care ' + gidin = chkPrompt(('** What GID should {0} have? Leave this blank if you don\'t care ' +
'(should be fine for most cases): ').format(grpin), syshelp) '(should be fine for most cases): ').format(grpin), syshelp)
if gidin != '': if gidin != '':
try: try:
@ -238,13 +238,13 @@ class aifgen(object):
else: else:
users[user]['gid'] = False users[user]['gid'] = False
syshelp.append('https://aif.square-r00t.net/#code_home_code') syshelp.append('https://aif.square-r00t.net/#code_home_code')
homein = chkPrompt(('* What directory should {0} use for its home? Leave blank if you don\'t care ' + homein = chkPrompt(('** What directory should {0} use for its home? Leave blank if you don\'t care ' +
'(should be fine for most cases): ').format(user), syshelp) '(should be fine for most cases): ').format(user), syshelp)
if homein != '': if homein != '':
if not re.match('^/([^/\x00\s]+(/)?)+)$', homein): if not re.match('^/([^/\x00\s]+(/)?)+)$', homein):
exit('!! ERROR: Path {0} does not seem to be valid.'.format(homein)) exit('!! ERROR: Path {0} does not seem to be valid.'.format(homein))
users[user]['home'] = homein users[user]['home'] = homein
homecrt = chkPrompt('* Do we need to create {0}? (y/{1}n{2}) '.format(homein, color.BOLD, color.END), syshelp) homecrt = chkPrompt('*** Do we need to create {0}? (y/{1}n{2}) '.format(homein, color.BOLD, color.END), syshelp)
if re.match('^y(es)?$', homecrt): if re.match('^y(es)?$', homecrt):
users[user]['homecreate'] = True users[user]['homecreate'] = True
else: else:
@ -255,7 +255,7 @@ class aifgen(object):
xgrouphelp = 'https://aif.square-r00t.net/#code_xgroup_code' xgrouphelp = 'https://aif.square-r00t.net/#code_xgroup_code'
if xgrouphelp not in syshelp: if xgrouphelp not in syshelp:
syshelp.append(xgrouphelp) syshelp.append(xgrouphelp)
xgroupin = chkPrompt('* Would you like to add extra groups for {0}? (y/{1}n{2}) '.format(user, color.BOLD, color.END), syshelp) xgroupin = chkPrompt('** Would you like to add extra groups for {0}? (y/{1}n{2}) '.format(user, color.BOLD, color.END), syshelp)
if re.match('^y(es)?$', xgroupin.lower()): if re.match('^y(es)?$', xgroupin.lower()):
morexgroups = True morexgroups = True
users[user]['xgroups'] = {} users[user]['xgroups'] = {}
@ -263,16 +263,16 @@ class aifgen(object):
morexgroups = False morexgroups = False
users[user]['xgroups'] = False users[user]['xgroups'] = False
while morexgroups: while morexgroups:
xgrp = chkPrompt('** What is the name of the group you would like to add to {0}? '.format(user), syshelp) xgrp = chkPrompt('*** What is the name of the group you would like to add to {0}? '.format(user), syshelp)
if len(xgrp) > 32: if len(xgrp) > 32:
exit(' !! ERROR: Group names must be less than 32 characters.') exit(' !! ERROR: Group names must be less than 32 characters.')
if not re.match('^[a-z_][a-z0-9_-]*[$]?$', xgrp): if not re.match('^[a-z_][a-z0-9_-]*[$]?$', xgrp):
exit(' !! ERROR: Your group name does not match a valid pattern. See the man page for groupadd (\'CAVEATS\').') exit(' !! ERROR: Your group name does not match a valid pattern. See the man page for groupadd (\'CAVEATS\').')
users[user]['xgroups'][xgrp] = {} users[user]['xgroups'][xgrp] = {}
xgrpcrt = chkPrompt('** Does the group \'{0}\' need to be created? (y/{1}n{2}) '.format(xgrp, color.BOLD, color.END), syshelp) xgrpcrt = chkPrompt('*** Does the group \'{0}\' need to be created? (y/{1}n{2}) '.format(xgrp, color.BOLD, color.END), syshelp)
if re.match('^y(es)?$', xgrpcrt.lower()): if re.match('^y(es)?$', xgrpcrt.lower()):
users[user]['xgroups'][xgrp]['create'] = True users[user]['xgroups'][xgrp]['create'] = True
xgrpgid = chkPrompt(('** What GID should {0} be? If the group will already exist on the new system or ' + xgrpgid = chkPrompt(('*** What GID should {0} be? If the group will already exist on the new system or ' +
'don\'t care,\nleave this blank (should be fine for most cases): ').format(xgrp), syshelp) 'don\'t care,\nleave this blank (should be fine for most cases): ').format(xgrp), syshelp)
if xgrpgid != '': if xgrpgid != '':
try: try:
@ -284,9 +284,9 @@ class aifgen(object):
else: else:
users[user]['xgroups'][xgrp]['create'] = False users[user]['xgroups'][xgrp]['create'] = False
users[user]['xgroups'][xgrp]['gid'] = False users[user]['xgroups'][xgrp]['gid'] = False
morexgrpsin = input('* Would you like to add additional extra groups for {0}? (y/{1}n{2}) '.format(user, morexgrpsin = input('** Would you like to add additional extra groups for {0}? (y/{1}n{2}) '.format(user,
color.BOLD, color.BOLD,
color.END)) color.END))
if not re.match('^y(es)?$', morexgrpsin.lower()): if not re.match('^y(es)?$', morexgrpsin.lower()):
morexgroups = False morexgroups = False
moreusersin = chkPrompt('* Would you like to add additional users? (y/{0}n{1}) '.format(color.BOLD, color.END), syshelp) moreusersin = chkPrompt('* Would you like to add additional users? (y/{0}n{1}) '.format(color.BOLD, color.END), syshelp)
@ -329,7 +329,7 @@ class aifgen(object):
'multilib': {'mirror': 'file:///etc/pacman.d/mirrorlist', 'multilib': {'mirror': 'file:///etc/pacman.d/mirrorlist',
'siglevel': 'default', 'siglevel': 'default',
'enabled': False}} 'enabled': False}}
chkdefs = chkPrompt(('Would you like to review the default repository configuration ' + chkdefs = chkPrompt(('* Would you like to review the default repository configuration ' +
'(and possibly edit it)? ({0}y{1}/n) ').format(color.BOLD, color.END), repohelp) '(and possibly edit it)? ({0}y{1}/n) ').format(color.BOLD, color.END), repohelp)
fmtstr = '{0} {1:<20} {2:^10} {3:^10} {4}' # ('#', 'REPO', 'ENABLED', 'SIGLEVEL', 'URI') fmtstr = '{0} {1:<20} {2:^10} {3:^10} {4}' # ('#', 'REPO', 'ENABLED', 'SIGLEVEL', 'URI')
if not re.match('^no?$', chkdefs.lower()): if not re.match('^no?$', chkdefs.lower()):
@ -338,23 +338,23 @@ class aifgen(object):
for r in repos.keys(): for r in repos.keys():
print(fmtstr.format(rcnt, r, str(repos[r]['enabled']), repos[r]['siglevel'], repos[r]['mirror'])) print(fmtstr.format(rcnt, r, str(repos[r]['enabled']), repos[r]['siglevel'], repos[r]['mirror']))
rcnt += 1 rcnt += 1
editdefs = chkPrompt('Would you like to edit any of this? (y/{0}n{1}) '.format(color.BOLD, color.END), repohelp) editdefs = chkPrompt('** Would you like to edit any of this? (y/{0}n{1}) '.format(color.BOLD, color.END), repohelp)
if re.match('^y(es)?$', editdefs.lower()): if re.match('^y(es)?$', editdefs.lower()):
repokeys = list(repos.keys()) repokeys = list(repos.keys())
moreedits = True moreedits = True
while moreedits: while moreedits:
rnum = input('* What repository # would you like to edit? ') rnum = input('** What repository # would you like to edit? ')
try: try:
rnum = int(rnum) rnum = int(rnum)
rname = repokeys[rnum - 1] rname = repokeys[rnum - 1]
except: except:
exit(' !! ERROR: You did not specify a valid repository #.') exit(' !! ERROR: You did not specify a valid repository #.')
enableedit = chkPrompt('** Should {0} be enabled? (y/n/{1}nochange{2}) '.format(rname, color.BOLD, color.END), repohelp) enableedit = chkPrompt('*** Should {0} be enabled? (y/n/{1}nochange{2}) '.format(rname, color.BOLD, color.END), repohelp)
if re.match('^y(es)?$', enableedit.lower()): if re.match('^y(es)?$', enableedit.lower()):
repos[rname]['enabled'] = True repos[rname]['enabled'] = True
elif re.match('^no?$', enableedit.lower()): elif re.match('^no?$', enableedit.lower()):
repos[rname]['enabled'] = False repos[rname]['enabled'] = False
siglvledit = chkPrompt('** What siglevel should {0} use? Leave blank for no change: ', repohelp) siglvledit = chkPrompt('*** What siglevel should {0} use? Leave blank for no change: '.format(rname), repohelp)
if siglvledit != '': if siglvledit != '':
grp1 = re.compile('^((Package|Database)?(Never|Optional|Required)|default)$') grp1 = re.compile('^((Package|Database)?(Never|Optional|Required)|default)$')
grp2 = re.compile('^(Package|Database)?Trust(edOnly|All)$') grp2 = re.compile('^(Package|Database)?Trust(edOnly|All)$')
@ -370,8 +370,8 @@ class aifgen(object):
exit((' !! ERROR: {0} is not valid. See the manpage for pacman.conf ' + exit((' !! ERROR: {0} is not valid. See the manpage for pacman.conf ' +
'(\'PACKAGE AND DATABASE SIGNATURE CHECKING\').').format(siglist[1])) '(\'PACKAGE AND DATABASE SIGNATURE CHECKING\').').format(siglist[1]))
repos[rname]['siglevel'] = siglvledit repos[rname]['siglevel'] = siglvledit
uriedit = chkPrompt('** What should the URI be?\n' + uriedit = chkPrompt('*** What should the URI be?\n' +
'\tUse a file:///absolute/path/to/file to use an Include, or leave blank for no change: ', repohelp) '\tUse \'file:///absolute/path/to/file\' to use an Include directive. Leave blank for no change: ', repohelp)
if uriedit != '': if uriedit != '':
repos[rname]['mirror'] = uriedit repos[rname]['mirror'] = uriedit
moreeditsin = chkPrompt(('** Would you like to edit another ' + moreeditsin = chkPrompt(('** Would you like to edit another ' +
@ -387,12 +387,13 @@ class aifgen(object):
if not re.match('^[a-z0-9]', reponame.lower()): if not re.match('^[a-z0-9]', reponame.lower()):
exit(' !! ERROR: That is not a valid repository name.') exit(' !! ERROR: That is not a valid repository name.')
repos[reponame] = {} repos[reponame] = {}
enablein = chkPrompt('** Should {0} be enabled? ({1}y{2}/n) '.format(reponame, color.BOLD, color.END), repohelp) enablein = chkPrompt('** Should {0}{1}{2} be enabled? ({0}y{2}/n) '.format(color.BOLD, reponame, color.END), repohelp)
if not re.match('^no?$', enablein.lower()): if not re.match('^no?$', enablein.lower()):
repos[reponame]['enabled'] = True repos[reponame]['enabled'] = True
else: else:
repos[reponame]['enabled'] = False repos[reponame]['enabled'] = False
siglvlin = chkPrompt('** What SigLevel string should we use for {0}? Leave blank for default: '.format(reponame), repohelp) siglvlin = chkPrompt(('** What SigLevel string should we use for {0}{1}{2}? ' +
'Leave blank for default: ').format(color.BOLD, reponame, color.END), repohelp)
if siglvlin != '': if siglvlin != '':
grp1 = re.compile('^((Package|Database)?(Never|Optional|Required)|default)$') grp1 = re.compile('^((Package|Database)?(Never|Optional|Required)|default)$')
grp2 = re.compile('^(Package|Database)?Trust(edOnly|All)$') grp2 = re.compile('^(Package|Database)?Trust(edOnly|All)$')
@ -410,8 +411,10 @@ class aifgen(object):
repos[reponame]['siglevel'] = siglvlin repos[reponame]['siglevel'] = siglvlin
else: else:
repos[reponame]['siglevel'] = 'default' repos[reponame]['siglevel'] = 'default'
uriin = chkPrompt(('** What URI should be used for {0}?\n' + uriin = chkPrompt(('** What URI should be used for {0}{1}{2}?\n' +
'\tUse a file:///absolute/path/to/file to use an Include: ').format(reponame), repohelp) '\tUse \'file:///absolute/path/to/file\' to use an Include directive: ').format(color.BOLD,
reponame,
color.END), repohelp)
if uriin == '': if uriin == '':
exit(' !! ERROR: You cannot specify a blank repository URI.') exit(' !! ERROR: You cannot specify a blank repository URI.')
else: else:
@ -492,8 +495,9 @@ class aifgen(object):
'(and other resources).') '(and other resources).')
# https://aif.square-r00t.net/#code_disk_code # https://aif.square-r00t.net/#code_disk_code
diskhelp = ['https://wiki.archlinux.org/index.php/installation_guide#Partition_the_disks'] diskhelp = ['https://wiki.archlinux.org/index.php/installation_guide#Partition_the_disks']
print('{0}= DISKS ={1}'.format(color.BOLD, color.END))
diskin = chkPrompt('\n* What disk(s) would you like to be configured on the target system?\n' + diskin = chkPrompt('\n* What disk(s) would you like to be configured on the target system?\n' +
'\tIf you have multiple disks, separate with a comma (e.g. \'/dev/sda,/dev/sdb\'): ', diskhelp) '\tIf you have multiple disks, separate with a comma (e.g. \'/dev/sda,/dev/sdb\'): ', diskhelp)
# NOTE: the following is a dict of fstype codes to their description. # 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'} 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'}
conf['disks'] = {} conf['disks'] = {}
@ -502,8 +506,8 @@ class aifgen(object):
if not re.match('^/dev/[A-Za-z0]+', disk): if not re.match('^/dev/[A-Za-z0]+', disk):
exit('!! ERROR: Disk {0} does not seem to be a valid device path.'.format(disk)) exit('!! ERROR: Disk {0} does not seem to be a valid device path.'.format(disk))
conf['disks'][disk] = {} conf['disks'][disk] = {}
print('\n* Configuring disk {0} ...'.format(disk)) print('\n{0}== DISK: {1} =={2}'.format(color.BOLD, disk, color.END))
fmtin = chkPrompt('** What format should this disk use (gpt/bios)? ', diskhelp) fmtin = chkPrompt('* What format should this disk use (gpt/bios)? ', diskhelp)
fmt = fmtin.lower() fmt = fmtin.lower()
if fmt not in ('gpt', 'bios'): if fmt not in ('gpt', 'bios'):
exit(' !! ERROR: Must be one of \'gpt\' or \'bios\'.') exit(' !! ERROR: Must be one of \'gpt\' or \'bios\'.')
@ -513,7 +517,7 @@ class aifgen(object):
maxpart = '256' maxpart = '256'
else: else:
maxpart = '4' # yeah, extended volumes can do more, but that's not supported in AIF-NG. yet? maxpart = '4' # yeah, extended volumes can do more, but that's not supported in AIF-NG. yet?
partnumsin = chkPrompt('** How many partitions should this disk have? (Maximum: {0}) '.format(maxpart), diskhelp) partnumsin = chkPrompt('* How many partitions should this disk have? (Maximum: {0}) '.format(maxpart), diskhelp)
try: try:
int(partnumsin) int(partnumsin)
except: except:
@ -527,15 +531,16 @@ class aifgen(object):
for partn in range(1, int(partnumsin) + 1): for partn in range(1, int(partnumsin) + 1):
# https://aif.square-r00t.net/#code_part_code # https://aif.square-r00t.net/#code_part_code
conf['disks'][disk]['parts'][partn] = {} conf['disks'][disk]['parts'][partn] = {}
print('{0}=== PARTITION: {1}{2}==={3}'.format(color.BOLD, disk, partn, color.END))
for s in ('start', 'stop'): for s in ('start', 'stop'):
conf['disks'][disk]['parts'][partn][s] = None conf['disks'][disk]['parts'][partn][s] = None
sizein = chkPrompt(('** Where should partition {0} {1}? Can be percentage [n%] ' + sizein = chkPrompt(('* Where should partition {0} {1}? Can be percentage [n%] ' +
'or size [(+/-)n(K/M/G/T/P)]: ').format(partn, s), parthelp) 'or size [(+/-)n(K/M/G/T/P)]: ').format(partn, s), parthelp)
conf['disks'][disk]['parts'][partn][s] = sizeChk(sizein) conf['disks'][disk]['parts'][partn][s] = sizeChk(sizein)
newhelp = 'https://aif.square-r00t.net/#fstypes' newhelp = 'https://aif.square-r00t.net/#fstypes'
if newhelp not in parthelp: if newhelp not in parthelp:
parthelp.append(newhelp) parthelp.append(newhelp)
fstypein = chkPrompt(('** What filesystem type should partition {0} be? ' + fstypein = chkPrompt(('* What filesystem type should partition {0} be? ' +
'See wikihelp for valid fstypes: ').format(partn), parthelp) 'See wikihelp for valid fstypes: ').format(partn), parthelp)
if fstypein not in fstypes.keys(): if fstypein not in fstypes.keys():
exit(' !! ERROR: {0} is not a valid filesystem type.'.format(fstypein)) exit(' !! ERROR: {0} is not a valid filesystem type.'.format(fstypein))
@ -543,6 +548,7 @@ class aifgen(object):
print('\t(Selected {0})'.format(fstypes[fstypein])) print('\t(Selected {0})'.format(fstypes[fstypein]))
mnthelp = ['https://wiki.archlinux.org/index.php/installation_guide#Mount_the_file_systems', mnthelp = ['https://wiki.archlinux.org/index.php/installation_guide#Mount_the_file_systems',
'https://aif.square-r00t.net/#code_mount_code'] 'https://aif.square-r00t.net/#code_mount_code']
print('{0}= MOUNTS ={1}'.format(color.BOLD, color.END))
mntin = chkPrompt('\n* What mountpoint(s) would you like to be configured on the target system?\n' + mntin = chkPrompt('\n* What mountpoint(s) would you like to be configured on the target system?\n' +
'\tIf you have multiple mountpoints, separate with a comma (e.g. \'/mnt/aif,/mnt/aif/boot\').\n' + '\tIf you have multiple mountpoints, separate with a comma (e.g. \'/mnt/aif,/mnt/aif/boot\').\n' +
'\t(NOTE: Can be \'swap\' for swapspace.): ', mnthelp) '\t(NOTE: Can be \'swap\' for swapspace.): ', mnthelp)
@ -551,11 +557,11 @@ class aifgen(object):
mount = m.strip() mount = m.strip()
if not re.match('^(/([^/\x00\s]+(/)?)+|swap)$', mount): if not re.match('^(/([^/\x00\s]+(/)?)+|swap)$', mount):
exit('!! ERROR: Mountpoint {0} does not seem to be a valid path/specifier.'.format(mount)) exit('!! ERROR: Mountpoint {0} does not seem to be a valid path/specifier.'.format(mount))
print('\n* Configuring mountpoint {0} ...'.format(mount)) print('\n{0}==MOUNT {1}=={2}'.format(color.BOLD, mount, color.END))
dvcin = chkPrompt('** What device/partition should be mounted here? ', mnthelp) dvcin = chkPrompt('* What device/partition should be mounted here? ', mnthelp)
if not re.match('^/dev/[A-Za-z0]+', dvcin): if not re.match('^/dev/[A-Za-z0]+', dvcin):
exit(' !! ERROR: Must be a full path to a device/partition.') exit(' !! ERROR: Must be a full path to a device/partition.')
ordrin = chkPrompt('** What order should this mount occur in relation to others?\n\t'+ ordrin = chkPrompt('* What order should this mount occur in relation to others?\n\t'+
'Must be a unique integer (lower numbers mount before higher numbers): ', mnthelp) 'Must be a unique integer (lower numbers mount before higher numbers): ', mnthelp)
try: try:
order = int(ordrin) order = int(ordrin)
@ -567,7 +573,7 @@ class aifgen(object):
conf['mounts'][order]['target'] = mount conf['mounts'][order]['target'] = mount
conf['mounts'][order]['device'] = dvcin conf['mounts'][order]['device'] = dvcin
if mount != 'swap': if mount != 'swap':
fstypein = chkPrompt('** What filesystem type should this be mounted as (i.e. mount\'s -t option)? This is optional,\n\t' + fstypein = chkPrompt('* What filesystem type should this be mounted as (i.e. mount\'s -t option)? This is optional,\n\t' +
'but may be required for more exotic filesystem types. If you don\'t have to specify one,\n\t' + 'but may be required for more exotic filesystem types. If you don\'t have to specify one,\n\t' +
'just leave this blank: ', mnthelp) 'just leave this blank: ', mnthelp)
if fstypein == '': if fstypein == '':
@ -588,8 +594,8 @@ class aifgen(object):
else: else:
conf['mounts'][order]['fstype'] = False conf['mounts'][order]['fstype'] = False
conf['mounts'][order]['opts'] = False conf['mounts'][order]['opts'] = False
print('\nNow, let\'s configure the network. Note that at this time,' + print(('\n{0}= NETWORK ={1}\n' +
'\twireless/more exotic networking is not supported by AIF-NG.\n') '\tNOTE: At this time, wireless/more exotic networking is not supported by AIF-NG.').format(color.BOLD, color.END))
conf['network'] = {} conf['network'] = {}
nethelp = ['https://wiki.archlinux.org/index.php/installation_guide#Network_configuration', nethelp = ['https://wiki.archlinux.org/index.php/installation_guide#Network_configuration',
'https://aif.square-r00t.net/#code_network_code'] 'https://aif.square-r00t.net/#code_network_code']
@ -609,7 +615,7 @@ class aifgen(object):
conf['network']['ifaces'] = {} conf['network']['ifaces'] = {}
nethelp.append('https://aif.square-r00t.net/#code_iface_code') nethelp.append('https://aif.square-r00t.net/#code_iface_code')
conf['network']['ifaces'] = ifacePrompt(nethelp) conf['network']['ifaces'] = ifacePrompt(nethelp)
print('\nNow let\'s configure some basic system settings.') print('\n{0}= SYSTEM ={1}'.format(color.BOLD, color.END))
syshelp = ['https://aif.square-r00t.net/#code_system_code'] syshelp = ['https://aif.square-r00t.net/#code_system_code']
syshelp.append('https://wiki.archlinux.org/index.php/installation_guide#Time_zone') syshelp.append('https://wiki.archlinux.org/index.php/installation_guide#Time_zone')
tzin = chkPrompt('* What timezone should the newly installed system use? (Default is UTC): ', syshelp) tzin = chkPrompt('* What timezone should the newly installed system use? (Default is UTC): ', syshelp)
@ -635,12 +641,13 @@ class aifgen(object):
rebootme = False rebootme = False
conf['system'] = {'timezone': tzin, 'locale': localein, 'chrootpath': chrootpathin, 'kbd': kbdin, 'reboot': rebootme} conf['system'] = {'timezone': tzin, 'locale': localein, 'chrootpath': chrootpathin, 'kbd': kbdin, 'reboot': rebootme}
syshelp.append('https://aif.square-r00t.net/#code_users_code') syshelp.append('https://aif.square-r00t.net/#code_users_code')
print('\nNow let\'s handle some user accounts. For passwords, you can either enter the password you want to use,\n' + print(('\n{0}== USERS =={1}\n\tNOTE: For passwords, you can either enter the password you want to use,\n' +
'a \'!\' (in which case TTY login will be disabled but e.g. SSH will still work), or just hit enter to leave it blank\n' + '\ta \'!\' (in which case TTY login will be disabled but e.g. SSH will still work), or just hit enter to leave it blank\n' +
'(which is HIGHLY not recommended - it means anyone can login by just pressing enter at the login!)\n') '\t(which is HIGHLY not recommended - it means anyone can login by just pressing enter at the login!)').format(color.BOLD, color.END))
print('Let\'s configure the root user.') print('{0}=== ROOT ==={1}'.format(color.BOLD, color.END))
conf['system']['rootpass'] = genPassHash('root') conf['system']['rootpass'] = genPassHash('root')
moreusers = input('Would you like to add one or more regular user(s)? (y/{0}n{1}) '.format(color.BOLD, color.END)) print('{0}=== REGULAR USERS ==={1}'.format(color.BOLD, color.END))
moreusers = input('* Would you like to add regular user(s)? (y/{0}n{1}) '.format(color.BOLD, color.END))
if re.match('^y(es)?$', moreusers.lower()): if re.match('^y(es)?$', moreusers.lower()):
syshelp.append('https://aif.square-r00t.net/#code_user_code') syshelp.append('https://aif.square-r00t.net/#code_user_code')
conf['system']['users'] = userPrompt(syshelp) conf['system']['users'] = userPrompt(syshelp)
@ -648,31 +655,32 @@ class aifgen(object):
conf['system']['users'] = False conf['system']['users'] = False
svchelp = ['https://wiki.archlinux.org/index.php/Systemd', svchelp = ['https://wiki.archlinux.org/index.php/Systemd',
'https://aif.square-r00t.net/#code_service_code'] 'https://aif.square-r00t.net/#code_service_code']
svcin = chkPrompt('Would you like to configure (enable/disable) services? (y/{0}n{1}) '.format(color.BOLD, color.END), svchelp) print('{0}== SERVICES =={1}'.format(color.BOLD, color.END))
svcin = chkPrompt('* Would you like to configure (enable/disable) services? (y/{0}n{1}) '.format(color.BOLD, color.END), svchelp)
if re.match('^y(es)?$', svcin.lower()): if re.match('^y(es)?$', svcin.lower()):
conf['system']['services'] = svcsPrompt(svchelp) conf['system']['services'] = svcsPrompt(svchelp)
else: else:
conf['system']['services'] = False conf['system']['services'] = False
print('\nNow let\'s configure the package management.') print('\n{0}== PACKAGES/SOFTWARE =={1}'.format(color.BOLD, color.END))
conf['software'] = {} conf['software'] = {}
pkgrhelp = ['https://wiki.archlinux.org/index.php/Pacman', pkgrhelp = ['https://wiki.archlinux.org/index.php/Pacman',
'https://wiki.archlinux.org/index.php/AUR_helpers', 'https://wiki.archlinux.org/index.php/AUR_helpers',
'https://aif.square-r00t.net/#code_pacman_code'] 'https://aif.square-r00t.net/#code_pacman_code']
pkgrcmd = chkPrompt('If you won\'t be using pacman for a package manager, what command should be used to install packages?\n' + pkgrcmd = chkPrompt('* If you won\'t be using pacman for a package manager, what command should be used to install packages?\n' +
'\t(Remember that you would need to install/configure it in a \'pkg\' hook script.)\n' + '\t(Remember that you would need to install/configure it in a \'pkg\' hook script.)\n' +
'\tLeave blank if you\'ll only be using pacman: ', pkgrhelp) '\tLeave blank if you\'ll only be using pacman: ', pkgrhelp)
if pkgrcmd == '': if pkgrcmd == '':
conf['software']['pkgr'] = False conf['software']['pkgr'] = False
else: else:
conf['software']['pkgr'] = pkgrcmd conf['software']['pkgr'] = pkgrcmd
print('\nWe need to configure the repositories for pacman and other software options.') print('\n{0}=== REPOSITORIES/PACKAGES ==={1}'.format(color.BOLD, color.END))
repohelp = ['https://aif.square-r00t.net/#code_repos_code'] repohelp = ['https://aif.square-r00t.net/#code_repos_code']
conf['software']['repos'] = repoPrompt(repohelp) conf['software']['repos'] = repoPrompt(repohelp)
pkgsin = chkPrompt(('* Would you like to have extra packages installed?\n' + if pkgrcmd == '':
pkgrcmd = 'pacman --needed --noconfirm -S'
pkgsin = chkPrompt(('* Would you like to install extra packages?\n' +
'\t(Note that they must be available in your configured repositories or\n' + '\t(Note that they must be available in your configured repositories or\n' +
'\tinstallable via "{0} <package name>".) (y/{0}n{1}) ').format(conf['software']['pkgr'], '\tinstallable via "{0} <package name>".) (y/{1}n{2}) ').format(pkgrcmd, color.BOLD, color.END), repohelp)
color.BOLD,
color.END), repohelp)
if re.match('^y(es)?$', pkgsin.lower()): if re.match('^y(es)?$', pkgsin.lower()):
repohelp.append('https://aif.square-r00t.net/#code_package_code') repohelp.append('https://aif.square-r00t.net/#code_package_code')
conf['software']['packages'] = pkgsPrompt(repohelp) conf['software']['packages'] = pkgsPrompt(repohelp)
@ -681,7 +689,8 @@ class aifgen(object):
btldrhelp = ['https://wiki.archlinux.org/index.php/installation_guide#Boot_loader', btldrhelp = ['https://wiki.archlinux.org/index.php/installation_guide#Boot_loader',
'https://aif.square-r00t.net/#code_bootloader_code'] 'https://aif.square-r00t.net/#code_bootloader_code']
conf['boot'] = {} conf['boot'] = {}
btldrin = chkPrompt('* Almost done! Please choose a bootloader. ({0}grub{1}/systemd) '.format(color.BOLD, color.END), btldrhelp) print('{0}== BOOTLOADER =={1}'.format(color.BOLD, color.END))
btldrin = chkPrompt('* Please choose a bootloader. ({0}grub{1}/systemd) '.format(color.BOLD, color.END), btldrhelp)
if btldrin == '': if btldrin == '':
btldrin = 'grub' btldrin = 'grub'
elif not re.match('^(grub|systemd)$', btldrin.lower()): elif not re.match('^(grub|systemd)$', btldrin.lower()):
@ -704,7 +713,8 @@ class aifgen(object):
else: else:
conf['boot']['target'] = bttgtin conf['boot']['target'] = bttgtin
scrpthlp = ['https://aif.square-r00t.net/#code_script_code'] scrpthlp = ['https://aif.square-r00t.net/#code_script_code']
scrptsin = chkPrompt('* Last one! Do you have any hook scripts you\'d like to add? (y/{0}n{1}) '.format(color.BOLD, color.END), scrpthlp) print('{0}= HOOK SCRIPTS ={1}'.format(color.BOLD, color.END))
scrptsin = chkPrompt('* Do you have any hook scripts you\'d like to add? (y/{0}n{1}) '.format(color.BOLD, color.END), scrpthlp)
if re.match('^y(es)?$', scrptsin.lower()): if re.match('^y(es)?$', scrptsin.lower()):
conf['scripts'] = scrptPrompt(scrpthlp) conf['scripts'] = scrptPrompt(scrpthlp)
print('\n\n{0}ALL DONE!{1} Whew. You can find your configuration file at: {2}{3}{1}\n'.format(color.BOLD, print('\n\n{0}ALL DONE!{1} Whew. You can find your configuration file at: {2}{3}{1}\n'.format(color.BOLD,
@ -741,12 +751,21 @@ class aifgen(object):
print('XML: {0}PASSED{1}\n'.format(color.BOLD, color.END)) print('XML: {0}PASSED{1}\n'.format(color.BOLD, color.END))
except Exception as e: except Exception as e:
print('XML: {0}FAILED{1}: {2}\n'.format(color.BOLD, color.END, e)) print('XML: {0}FAILED{1}: {2}\n'.format(color.BOLD, color.END, e))

def genXMLFile(self, conf):
if lxml_avail:
root = etree.Element('aif')
else:
root = etree.ElementTree.Element('aif')
pass

def main(self): def main(self):
if self.args['oper'] == 'create': if self.args['oper'] == 'create':
conf = self.getOpts() conf = self.getOpts()
elif self.args['oper'] == 'convert': elif self.args['oper'] == 'convert':
conf = self.convertJSON() conf = self.convertJSON()
if self.args['oper'] in ('create', 'convert'):
self.genXMLFile(conf)
if self.args['oper'] in ('create', 'convert', 'validate'): if self.args['oper'] in ('create', 'convert', 'validate'):
self.validateXML() self.validateXML()


@ -824,8 +843,6 @@ def main():
if not args['oper']: if not args['oper']:
parseArgs().print_help() parseArgs().print_help()
else: else:
# Once aifgen.main() is complete, we only need to call that.
# That should handle all the below logic.
aif = aifgen(verifyArgs(args)) aif = aifgen(verifyArgs(args))
aif.main() aif.main()



View File

@ -30,7 +30,6 @@ also create:
-create boot media with bdisk since default arch doesn't even have python 3 -create boot media with bdisk since default arch doesn't even have python 3
-- this is.. sort of? done. but iPXE/mini build is failing, need to investigate why -- this is.. sort of? done. but iPXE/mini build is failing, need to investigate why



docs: docs:
http://lxml.de/parsing.html http://lxml.de/parsing.html
https://www.w3.org/2001/XMLSchema.xsd https://www.w3.org/2001/XMLSchema.xsd

View File

@ -11,7 +11,7 @@ if {$force_conservative} {
} }
} }


set send_slow {10 .001} #set send_slow {10 .001}


set timeout -1 set timeout -1
spawn ./aif-config.py create -v:r -f /tmp/aif.xml spawn ./aif-config.py create -v:r -f /tmp/aif.xml
@ -96,7 +96,10 @@ send -- "\r"
# reboot host after install? default is yes # reboot host after install? default is yes
send -- "\r" send -- "\r"
# root password # root password
sleep 2
send -- "test\r" send -- "test\r"
sleep 2
expect *
# add user? # add user?
send -- "y\r" send -- "y\r"
# user # user
@ -104,7 +107,9 @@ send -- "aifusr\r"
# sudo access # sudo access
send -- "y\r" send -- "y\r"
# password # password
sleep 2
send -- "test\r" send -- "test\r"
sleep 2
send -- "A Test User\r" send -- "A Test User\r"
# uid (default is autogen) # uid (default is autogen)
send -- "\r" send -- "\r"