hoo man. heads up, those password hashes are all just "test"

This commit is contained in:
brent s 2017-05-14 01:21:15 -04:00
parent abaf931f37
commit 4172111fcd
5 changed files with 543 additions and 171 deletions

View File

@ -11,6 +11,7 @@ import crypt
import datetime import datetime
import errno import errno
import ipaddress import ipaddress
import json
import getpass import getpass
import os import os
import re import re
@ -176,7 +177,9 @@ class aifgen(object):
moreIfaces = False moreIfaces = False
return(ifaces) return(ifaces)
def genPassHash(user): def genPassHash(user):
passin = getpass.getpass('* Please enter the password you want to use for {0} (will not echo back): '.format(user)) # 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 = 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)
@ -185,15 +188,15 @@ class aifgen(object):
return(salthash) return(salthash)
def userPrompt(syshelp): def userPrompt(syshelp):
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/{0}n{1}) '.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:
@ -214,18 +217,24 @@ class aifgen(object):
'(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)
if len(grpin) > 32: if grpin != '':
exit(' !! ERROR: Group names must be less than 32 characters.') if len(grpin) > 32:
if not re.match('^[a-z_][a-z0-9_-]*[$]?$', grpin): exit(' !! ERROR: Group names must be less than 32 characters.')
exit(' !! ERROR: Your group name does not match a valid pattern. See the man page for groupadd (\'CAVEATS\').') if not re.match('^[a-z_][a-z0-9_-]*[$]?$', grpin):
users[user]['group'] = grpin exit(' !! ERROR: Your group name does not match a valid pattern. See the man page for groupadd (\'CAVEATS\').')
gidin = chkPrompt(('* What GID should {0} have? Leave this blank if you don\'t care ' + users[user]['group'] = grpin
'(should be fine for most cases): ').format(grpin), syshelp) else:
if gidin != '': users[user]['group'] = False
try: if grpin != '':
users[user]['gid'] = int(gidin) gidin = chkPrompt(('* What GID should {0} have? Leave this blank if you don\'t care ' +
except: '(should be fine for most cases): ').format(grpin), syshelp)
exit(' !! ERROR: The GID must be an integer.') if gidin != '':
try:
users[user]['gid'] = int(gidin)
except:
exit(' !! ERROR: The GID must be an integer.')
else:
users[user]['gid'] = False
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')
@ -235,13 +244,13 @@ class aifgen(object):
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)
if re.match('^y(es)?$', homecrt):
users[user]['homecreate'] = True
else:
users[user]['homecreate'] = False
else: else:
users[user]['home'] = False users[user]['home'] = False
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):
users[user]['homecreate'] = True
else:
users[user]['homecreate'] = False
del(syshelp[-1]) del(syshelp[-1])
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:
@ -254,29 +263,35 @@ 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? ', 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 {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 ' +
'don\'t care,\nleave this blank (should be fine for most cases): ').format(xgrp), syshelp)
if xgrpgid != '':
try:
users[user]['xgroups'][xgrp]['gid'] = int(xgrpgid)
except:
exit(' !! ERROR: The GID must be an integer.')
else:
users[user]['xgroups'][xgrp]['gid'] = False
else: else:
users[user]['xgroups'][xgrp]['create'] = False users[user]['xgroups'][xgrp]['create'] = False
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)
if xrpgid != '':
try:
users[user]['xgroups'][xgrp]['gid'] = int(xgrpid)
except:
exit(' !! ERROR: The GID must be an integer.')
else:
users[user]['xgroups'][xgrp]['gid'] = False users[user]['xgroups'][xgrp]['gid'] = False
moreusersin = input('* Would you like to add additional extra groups for {0}? (y/{1}n{2}) '.format(user, color.BOLD, color.END)) morexgrpsin = input('* Would you like to add additional extra groups for {0}? (y/{1}n{2}) '.format(user,
if not re.match('^y(es)?$', moreusersin.lower()): color.BOLD,
color.END))
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)
if not re.match('^y(es)?$', moreusersin.lower()):
moreusers = False
return(users) return(users)
def svcsPrompt(svchelp): def svcsPrompt(svchelp):
svcs = {} svcs = {}
@ -289,7 +304,7 @@ class aifgen(object):
if re.match('^no?$', svcstatusin.lower()): if re.match('^no?$', svcstatusin.lower()):
svcs[svc] = False svcs[svc] = False
else: else:
svcs[svc] - True svcs[svc] = True
moreservices = input('* Would you like to manage another service? (y/{0}n{1}) '.format(color.BOLD, color.END)) moreservices = input('* Would you like to manage another service? (y/{0}n{1}) '.format(color.BOLD, color.END))
if not re.match('^y(es)?$', moreservices.lower()): if not re.match('^y(es)?$', moreservices.lower()):
moresvcs = False moresvcs = False
@ -474,10 +489,10 @@ class aifgen(object):
conf = {} conf = {}
print('[{0}] Beginning configuration...'.format(datetime.datetime.now())) print('[{0}] Beginning configuration...'.format(datetime.datetime.now()))
print('You may reply with \'wikihelp\' on the first prompt of a question for the relevant link(s) in the Arch wiki ' + print('You may reply with \'wikihelp\' on the first prompt of a question for the relevant link(s) in the Arch wiki ' +
'(and other resources).\n') '(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']
diskin = chkPrompt('\nWhat 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'}
@ -487,8 +502,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('\nConfiguring disk {0} ...'.format(disk)) print('\n* Configuring disk {0} ...'.format(disk))
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\'.')
@ -498,7 +513,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:
@ -525,10 +540,10 @@ class aifgen(object):
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))
else: else:
print('\tSelected {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']
mntin = chkPrompt('\nWhat 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)
conf['mounts'] = {} conf['mounts'] = {}
@ -536,11 +551,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('\nConfiguring mountpoint {0} ...'.format(mount)) print('\n* Configuring mountpoint {0} ...'.format(mount))
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)
@ -551,30 +566,35 @@ class aifgen(object):
conf['mounts'][order] = {} conf['mounts'][order] = {}
conf['mounts'][order]['target'] = mount conf['mounts'][order]['target'] = mount
conf['mounts'][order]['device'] = dvcin conf['mounts'][order]['device'] = dvcin
fstypein = chkPrompt('* What filesystem type should this be mounted as (i.e. mount\'s -t option)? This is optional,\n\t' + if mount != 'swap':
'but may be required for more exotic filesystem types. If you don\'t have to specify one,\n\t' + fstypein = chkPrompt('** What filesystem type should this be mounted as (i.e. mount\'s -t option)? This is optional,\n\t' +
'just leave this blank: ', mnthelp) 'but may be required for more exotic filesystem types. If you don\'t have to specify one,\n\t' +
if fstypein == '': 'just leave this blank: ', mnthelp)
if fstypein == '':
conf['mounts'][order]['fstype'] = False
elif not re.match('^[a-z]+([0-9]+)?$', fstypein): # Not 100%, but should catch most faulty entries
exit(' !! ERROR: {0} does not seem to be a valid filesystem type.'.format(fstypein))
else:
conf['mounts'][order]['fstype'] = fstypein
mntoptsin = chkPrompt('** What, if any, mount option(s) (mount\'s -o option) do you require? (Multiple options should be separated\n' +
'\twith a comma). If none, leave this blank: ', mnthelp)
if mntoptsin == '':
conf['mounts'][order]['opts'] = False
elif not re.match('^[A-Za-z0-9_\.\-=]+(,[A-Za-z0-9_\.\-=]+)*', re.sub('\s', '', mntoptsin)): # TODO: shlex split this instead?
exit(' !! ERROR: You seem to have not specified valid mount options.')
else:
# TODO: slex this instead? is it possible for mount opts to contain whitespace?
conf['mounts'][order]['opts'] = re.sub('\s', '', mntoptsin)
else:
conf['mounts'][order]['fstype'] = False conf['mounts'][order]['fstype'] = False
elif not re.match('^[a-z]+([0-9]+)?$', fstypein): # Not 100%, but should catch most faulty entries
exit(' !! ERROR: {0} does not seem to be a valid filesystem type.'.format(fstypein))
else:
conf['mounts'][order]['fstype'] = fstypein
mntoptsin = chkPrompt('* What, if any, mount option(s) (mount\'s -o option) do you require? (Multiple options should be separated\n' +
'\twith a comma). If none, leave this blank: ', mnthelp)
if mntoptsin == '':
conf['mounts'][order]['opts'] = False conf['mounts'][order]['opts'] = False
elif not re.match('^[A-Za-z0-9_\.\-=]+(,[A-Za-z0-9_\.\-=]+)*', re.sub('\s', '', mntoptsin)): # TODO: shlex split this instead? print('\nNow, let\'s configure the network. Note that at this time,' +
exit(' !! ERROR: You seem to have not specified valid mount options.') '\twireless/more exotic networking is not supported by AIF-NG.\n')
else:
# TODO: slex this instead? is it possible for mount opts to contain whitespace?
conf['mounts'][order]['opts'] = re.sub('\s', '', mntoptsin)
print('\nNow, let\'s configure the network. Note that at this time, wireless/more exotic networking is not supported by AIF-NG.\n')
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']
hostnamein = chkPrompt('What should the newly-installed system\'s hostname be?\n\t' + hostnamein = chkPrompt('* What should the newly-installed system\'s hostname be?\n' +
'It must be in FQDN format, but can be a non-existent domain: ', nethelp) '\tIt must be in FQDN format, but can be a non-existent domain: ', nethelp)
hostname = hostnamein.lower() hostname = hostnamein.lower()
if len(hostname) > 253: if len(hostname) > 253:
exit(' !! ERROR: A FQDN cannot be more than 253 characters (RFC 1035, 2.3.4)') exit(' !! ERROR: A FQDN cannot be more than 253 characters (RFC 1035, 2.3.4)')
@ -614,12 +634,12 @@ class aifgen(object):
else: else:
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[1] = '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('\nNow let\'s handle some user accounts. 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' + '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' +
'(which is HIGHLY not recommended - it means anyone can login by just pressing enter at the login!)\n') '(which is HIGHLY not recommended - it means anyone can login by just pressing enter at the login!)\n')
print('Let\'s configure the root user.') print('Let\'s configure the root user.')
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)) moreusers = input('Would you like to add one or more 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')
@ -662,7 +682,9 @@ class aifgen(object):
'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) btldrin = chkPrompt('* Almost done! Please choose a bootloader. ({0}grub{1}/systemd) '.format(color.BOLD, color.END), btldrhelp)
if not re.match('^(grub|systemd)$', btldrin.lower()): if btldrin == '':
btldrin = 'grub'
elif not re.match('^(grub|systemd)$', btldrin.lower()):
exit(' !! ERROR: You must choose a bootloader between grub or systemd.') exit(' !! ERROR: You must choose a bootloader between grub or systemd.')
else: else:
conf['boot']['bootloader'] = btldrin.lower() conf['boot']['bootloader'] = btldrin.lower()
@ -676,7 +698,7 @@ class aifgen(object):
conf['boot']['efi'] = True conf['boot']['efi'] = True
bttgtstr = 'ESP (EFI System Partition)' bttgtstr = 'ESP (EFI System Partition)'
btrgx = re.compile('^/([^/\x00\s]+(/)?)+$') btrgx = re.compile('^/([^/\x00\s]+(/)?)+$')
bttgtin = chkPrompt('** What is the target for {0}? That is, the path to the {1}: '.format(btldrin.lower(), bttgtstr), btldrhelp) bttgtin = chkPrompt('** What is the target for {0}? That is, the path to the {1} (within the chroot): '.format(btldrin.lower(), bttgtstr), btldrhelp)
if not btrgx.match(bttgtin): if not btrgx.match(bttgtin):
exit(' !! ERROR: That doesn\'t seem to be a valid {0}.'.format(bttgtstr)) exit(' !! ERROR: That doesn\'t seem to be a valid {0}.'.format(bttgtstr))
else: else:
@ -692,12 +714,14 @@ class aifgen(object):
if self.args['verbose']: if self.args['verbose']:
import pprint import pprint
pprint.pprint(conf) pprint.pprint(conf)
if self.args['verbose_raw']:
print(conf)
return(conf) return(conf)
def convertJSON(self): def convertJSON(self):
with open(args['inputfile'], 'r') as f: with open(self.args['inputfile'], 'r') as f:
try: try:
conf = json.loads(f.read()) conf = json.load(f)
except: except:
exit(' !! ERROR: {0} does not seem to be a strict JSON file.'.format(args['inputfile'])) exit(' !! ERROR: {0} does not seem to be a strict JSON file.'.format(args['inputfile']))
return(conf) return(conf)
@ -723,7 +747,7 @@ class aifgen(object):
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'): if self.args['oper'] in ('create', 'convert', 'validate'):
self.validateXML() self.validateXML()


def parseArgs(): def parseArgs():
@ -752,6 +776,11 @@ def parseArgs():
dest = 'verbose', dest = 'verbose',
action = 'store_true', action = 'store_true',
help = 'Print the dict of raw values used to create the XML. Mostly/only useful for debugging.') help = 'Print the dict of raw values used to create the XML. Mostly/only useful for debugging.')
createargs.add_argument('-v:r',
'--verbose-raw',
dest = 'verbose_raw',
action = 'store_true',
help = 'Like -v, but prints the unformatted dict.')
convertargs.add_argument('-i', convertargs.add_argument('-i',
'--input', '--input',
dest = 'inputfile', dest = 'inputfile',
@ -798,12 +827,7 @@ def main():
# Once aifgen.main() is complete, we only need to call that. # Once aifgen.main() is complete, we only need to call that.
# That should handle all the below logic. # That should handle all the below logic.
aif = aifgen(verifyArgs(args)) aif = aifgen(verifyArgs(args))
if args['oper'] == 'create': aif.main()
aif.getOpts()
if args['oper'] == 'validate':
aif.validateXML()
elif args['oper'] == 'convert':
aif.convertJSON()


if __name__ == '__main__': if __name__ == '__main__':
main() main()

View File

@ -1,97 +1,156 @@
{ {
"disks": "boot": {
{ "efi": true,
"/dev/sda": "target": "/boot"
{"fmt": "gpt", },
"parts": "disks": {
{ "/dev/sda": {
"1": "fmt": "gpt",
{ "parts": {
"start": "0%", "1": {
"stop": "90%" "start": "0%",
}, "stop": "95%"
"2": },
{ "2": {
"start": "90%", "start": "95%",
"stop": "100%" "stop": "100%"
} }
} }
}, },
"/dev/sdb": "/dev/sdb": {
{ "fmt": "gpt",
"fmt": "gpt", "parts": {
"parts": "1": {
{ "start": "0%",
"1": "stop": "47%"
{ },
"start": "0%", "2": {
"stop": "10%" "start": "47%",
}, "stop": "95%"
"2": },
{ "3": {
"start": "10%", "start": "95%",
"stop": "100%" "stop": "100%"
} }
} }
} }
}, },
"mounts": "mounts": {
{ "1": {
"1": "device": "/dev/sda1",
{ "fstype": "ext4",
"device": "/dev/sda1", "opts": "defaults",
"fstype": "ext4", "target": "/mnt/aif"
"opts": "rw,noatime,errors=remount-ro", },
"target": "/mnt/aif" "2": {
}, "device": "/dev/sda2",
"2": "fstype": "vfat",
{ "opts": "rw,relatime,fmask=0022,dmask=0022,codepage=437,iocharset=iso8859-1,shortname=mixed,errors=remount-ro",
"device": "/dev/sda2", "target": "/mnt/aif/boot"
"fstype": "vfat", },
"opts": "rw,relatime,fmask=0022,dmask=0022,codepage=437,iocharset=iso8859-1,shortname=mixed,errors=remount-ro,auto", "3": {
"target": "/mnt/aif/boot" "device": "/dev/sdb1",
}, "fstype": "ext4",
"3": "opts": "defaults",
{ "target": "/mnt/aif/home"
"device": "/dev/sdb1", },
"fstype": "swap", "4": {
"opts": false, "device": "/dev/sdb2",
"target": "swap"}, "fstype": "ext4",
"4": "opts": "defaults",
{ "target": "/mnt/aif/mnt/data"
"device": "/dev/sdb2", },
"fstype": false, "5": {
"opts": false, "device": "/dev/sdb3",
"target": "/mnt/aif/mnt/data" "fstype": false,
} "opts": false,
}, "target": "swap"
"network": }
{ },
"hostname": "aif.loc.lan", "network": {
"ifaces": "hostname": "aif.loc.lan",
{ "ifaces": {
"ens3": "ens3": {
{ "address": "auto",
"address": "auto", "gw": false,
"gw": false, "proto": "ipv4",
"proto": "ipv4", "resolvers": false
"resolvers": false },
}, "ens4": {
"ens4": "address": "192.168.1.2/24",
{ "gw": "192.168.1.1",
"address": "192.168.1.2/24", "proto": "ipv4",
"gw": "192.168.1.1", "resolvers": [
"proto": "ipv4", "4.2.2.1",
"resolvers": ["4.2.2.1", "4.2.2.2", "8.8.8.8"] "4.2.2.2"
} ]
} }
}, }
"system": },
{ "software": {
"chrootpath": "/mnt/aif", "packages": {
"kbd": "US", "openssh": "None"
"locale": "en_US.UTF-8", },
"reboot": true, "pkgr": false,
"timezone": "UTC" "repos": {
} "community": {
"enabled": true,
"mirror": "file:///etc/pacman.d/mirrorlist",
"siglevel": "default"
},
"community-testing": {
"enabled": false,
"mirror": "file:///etc/pacman.d/mirrorlist",
"siglevel": "default"
},
"core": {
"enabled": true,
"mirror": "file:///etc/pacman.d/mirrorlist",
"siglevel": "default"
},
"extra": {
"enabled": true,
"mirror": "file:///etc/pacman.d/mirrorlist",
"siglevel": "default"
},
"multilib": {
"enabled": true,
"mirror": "file:///etc/pacman.d/mirrorlist",
"siglevel": "default"
},
"multilib-testing": {
"enabled": false,
"mirror": "file:///etc/pacman.d/mirrorlist",
"siglevel": "default"
}
}
},
"system": {
"chrootpath": "/mnt/aif",
"kbd": "US",
"locale": "en_US.UTF-8",
"reboot": true,
"rootpass": "$6$0jk/xhwahQHTi5QP$VWTgGlHNdSBDbQmJXUwJPZqajfL3JqYYF7Ghxk3ZSKi12WWXb49KsjR7q0bigvgBBBk5A/mvYES3/qareytFS0",
"services": {
"sshd": true
},
"timezone": "UTC",
"users": {
"aifusr": {
"comment": "A Test User",
"gid": false,
"group": false,
"home": false,
"password": "$6$IlEwDkNmZRuTrT97$vKHjREGspspApBd8aQ/y1S43yRmGMjAzqOmdjNRLWaZyNKqGPrIjMHV9CJc7BzQgU12pRz3cwC6yyc8BDFARu/",
"sudo": true,
"uid": false,
"xgroups": {
"users": {
"create": false,
"gid": false
}
}
}
}
}
} }

View File

@ -0,0 +1,73 @@
{'boot': {'efi': True, 'target': '/boot'},
'disks': {'/dev/sda': {'fmt': 'gpt',
'parts': {1: {'start': '0%', 'stop': '95%'},
2: {'start': '95%', 'stop': '100%'}}},
'/dev/sdb': {'fmt': 'gpt',
'parts': {1: {'start': '0%', 'stop': '47%'},
2: {'start': '47%', 'stop': '95%'},
3: {'start': '95%', 'stop': '100%'}}}},
'mounts': {1: {'device': '/dev/sda1',
'fstype': 'ext4',
'opts': 'defaults',
'target': '/mnt/aif'},
2: {'device': '/dev/sda2',
'fstype': 'vfat',
'opts': 'rw,relatime,fmask=0022,dmask=0022,codepage=437,iocharset=iso8859-1,shortname=mixed,errors=remount-ro',
'target': '/mnt/aif/boot'},
3: {'device': '/dev/sdb1',
'fstype': 'ext4',
'opts': 'defaults',
'target': '/mnt/aif/home'},
4: {'device': '/dev/sdb2',
'fstype': 'ext4',
'opts': 'defaults',
'target': '/mnt/aif/mnt/data'},
5: {'device': '/dev/sdb3',
'fstype': False,
'opts': False,
'target': 'swap'}},
'network': {'hostname': 'aif.loc.lan',
'ifaces': {'ens3': {'address': 'auto',
'gw': False,
'proto': 'ipv4',
'resolvers': False},
'ens4': {'address': '192.168.1.2/24',
'gw': '192.168.1.1',
'proto': 'ipv4',
'resolvers': ['4.2.2.1', '4.2.2.2']}}},
'software': {'packages': {'openssh': 'None'},
'pkgr': False,
'repos': {'community': {'enabled': True,
'mirror': 'file:///etc/pacman.d/mirrorlist',
'siglevel': 'default'},
'community-testing': {'enabled': False,
'mirror': 'file:///etc/pacman.d/mirrorlist',
'siglevel': 'default'},
'core': {'enabled': True,
'mirror': 'file:///etc/pacman.d/mirrorlist',
'siglevel': 'default'},
'extra': {'enabled': True,
'mirror': 'file:///etc/pacman.d/mirrorlist',
'siglevel': 'default'},
'multilib': {'enabled': True,
'mirror': 'file:///etc/pacman.d/mirrorlist',
'siglevel': 'default'},
'multilib-testing': {'enabled': False,
'mirror': 'file:///etc/pacman.d/mirrorlist',
'siglevel': 'default'}}},
'system': {'chrootpath': '/mnt/aif',
'kbd': 'US',
'locale': 'en_US.UTF-8',
'reboot': True,
'rootpass': '$6$0jk/xhwahQHTi5QP$VWTgGlHNdSBDbQmJXUwJPZqajfL3JqYYF7Ghxk3ZSKi12WWXb49KsjR7q0bigvgBBBk5A/mvYES3/qareytFS0',
'services': {'sshd': True},
'timezone': 'UTC',
'users': {'aifusr': {'comment': 'A Test User',
'gid': False,
'group': False,
'home': False,
'password': '$6$IlEwDkNmZRuTrT97$vKHjREGspspApBd8aQ/y1S43yRmGMjAzqOmdjNRLWaZyNKqGPrIjMHV9CJc7BzQgU12pRz3cwC6yyc8BDFARu/',
'sudo': True,
'uid': False,
'xgroups': {'users': {'create': False,
'gid': False}}}}}}

167
extras/createtest.expect Executable file
View File

@ -0,0 +1,167 @@
#!/usr/bin/expect -f

log_file -noappend /tmp/expect.log
set force_conservative 0 ;# set to 1 to force conservative mode even if
;# script wasn't run conservatively originally
if {$force_conservative} {
set send_slow {1 .1}
proc send {ignore arg} {
sleep .1
exp_send -s -- $arg
}
}

set send_slow {10 .001}

set timeout -1
spawn ./aif-config.py create -v:r -f /tmp/aif.xml
## disks
send -- "/dev/sda,/dev/sdb\r"
# sda
send -- "gpt\r"
send -- "2\r"
# sda1
send -- "0%\r"
send -- "95%\r"
send -- "8300\r"
# sda2
send -- "95%\r"
send -- "100%\r"
send -- "ef00\r"
# sdb
send -- "gpt\r"
send -- "3\r"
# sdb1
send -- "0%\r"
send -- "47%\r"
send -- "8300\r"
# sdb2
send -- "47%\r"
send -- "95%\r"
send -- "8300\r"
# sdb3
send -- "95%\r"
send -- "100%\r"
send -- "8200\r"
## mounts
send -- "/mnt/aif,/mnt/aif/boot,/mnt/aif/home,/mnt/aif/mnt/data,swap\r"
# /mnt/aif
send -- "/dev/sda1\r"
send -- "1\r"
send -- "ext4\r"
send -- "defaults\r"
# /mnt/aif/boot
send -- "/dev/sda2\r"
send -- "2\r"
send -- "vfat\r"
send -- "rw,relatime,fmask=0022,dmask=0022,codepage=437,iocharset=iso8859-1,shortname=mixed,errors=remount-ro\r"
# /mnt/aif/home
send -- "/dev/sdb1\r"
send -- "3\r"
send -- "ext4\r"
send -- "defaults\r"
# /mnt/aif/mnt/data
send -- "/dev/sdb2\r"
send -- "4\r"
send -- "ext4\r"
send -- "defaults\r"
# swap
send -- "/dev/sdb3\r"
send -- "5\r"
## network
# hostname
send -- "aif.loc.lan\r"
# interface
send -- "ens3\r"
send -- "auto\r"
send -- "ipv4\r"
# add another interface?
send -- "y\r"
# second interface
send -- "ens4\r"
send -- "192.168.1.2/24\r"
send -- "192.168.1.1\r"
send -- "4.2.2.1,4.2.2.2\r"
# add another interface? default is no
send -- "\r"
## system
# timezone (default is UTC)
send -- "\r"
# locale (default is en_US.UTF-8
send -- "\r"
# chroot path
send -- "/mnt/aif\r"
# kbd (default is US)
send -- "\r"
# reboot host after install? default is yes
send -- "\r"
# root password
send -- "test\r"
# add user?
send -- "y\r"
# user
send -- "aifusr\r"
# sudo access
send -- "y\r"
# password
send -- "test\r"
send -- "A Test User\r"
# uid (default is autogen)
send -- "\r"
# primary group (default is autogen'd based on username)
send -- "\r"
# home dir (default is e.g. /home/username)
send -- "\r"
# add exta groups?
send -- "y\r"
# extra group
send -- "users\r"
# need to be created? default is no
send -- "\r"
# add another extra group? default is no
send -- "\r"
# add more users? default is no
send -- "\r"
# enable/disable services
send -- "y\r"
# service
send -- "sshd\r"
# enable? default is yes
send -- "\r"
# manage another service? default is no
send -- "\r"
# packager (default is pacman)
send -- "\r"
# review default repos? default is yes
send -- "\r"
# edit any of them?
send -- "y\r"
# edit the 6th repo (multilib)
send -- "6\r"
# enabled?
send -- "y\r"
# siglevel (default is unchanged)
send -- "\r"
# mirror URI (default is unchanged)
send -- "\r"
# edit another repo? default is no
send -- "\r"
# add additional repositories? default is no
send -- "\r"
# install extra software?
send -- "y\r"
# software
send -- "openssh\r"
# repository (optional)
send -- "\r"
# add another package?
send -- "\r"
# bootloader (default is grub)
send -- "\r"
# system supports UEFI? default is yes
send -- "\r"
# ESP/EFI system partition
send -- "/boot\r"
# any hook scripts? default is no
send -- "\r"
expect eof

49
extras/txttojson.py Executable file
View File

@ -0,0 +1,49 @@
#!/usr/bin/env python3

import argparse
import json
import os
import pprint
#import re
try:
import yaml
except:
exit('You need pyYAML.')

def parseArgs():
args = argparse.ArgumentParser()
args.add_argument('-i',
'--in',
dest = 'infile',
required = True,
help = 'The plaintext representation of a python dict')
args.add_argument('-o',
'--out',
dest = 'outfile',
required = True,
help = 'The JSON file to create')
return(args)

def main():
args = vars(parseArgs().parse_args())
infile = os.path.abspath(os.path.normpath(args['infile']))
outfile = os.path.abspath(os.path.normpath(args['outfile']))
if not os.path.lexists(infile):
exit('Input file doesn\'t exist.')
#try:
with open(outfile, 'w') as outgoing:
with open(infile, 'r') as incoming:
#data = re.sub("'", '"', incoming.read())
#outgoing.write(data)
#d = json.dumps(data, ensure_ascii = False)
#d = json.dumps(incoming.read().replace("'", '"'))
d = yaml.load(incoming.read())
pprint.pprint(d)
j = json.dumps(d, indent = 4)
outgoing.write(j)
#except:
#exit('Error when trying to read/write file(s).')
return()

if __name__ == '__main__':
main()