beginnings of aif-config tool
This commit is contained in:
parent
c16e365b04
commit
75f5f4a46d
151
aif-config.py
151
aif-config.py
@ -45,15 +45,56 @@ class aifgen(object):
|
|||||||
txtin = input(prompt)
|
txtin = input(prompt)
|
||||||
else:
|
else:
|
||||||
return(txtin)
|
return(txtin)
|
||||||
|
def sizeChk(startsize):
|
||||||
|
try:
|
||||||
|
startn = int(re.sub('[%\-+KMGTP])', '', startsize))
|
||||||
|
modifier = re.sub('^(\+|-)?.*$', '\g<1>', startsize)
|
||||||
|
if re.match('^(\+|-)?[0-9]+%$', n):
|
||||||
|
sizetype = 'percentage'
|
||||||
|
elif re.match('^(\+|-)?[0-9]+[KMGTP]$', n):
|
||||||
|
sizetype = 'fixed'
|
||||||
|
else:
|
||||||
|
exit(' !! ERROR: The input you provided does not match a valid pattern.')
|
||||||
|
if sizetype == 'percentage':
|
||||||
|
if int(startn) not in range(0, 100):
|
||||||
|
exit(' !! ERROR: You must provide a percentage or a size.')
|
||||||
|
except:
|
||||||
|
exit(' !! ERROR: You did not provide a valid size specifier!')
|
||||||
|
return(startsize)
|
||||||
|
def ifacePrompt(nethelp):
|
||||||
|
ifaces = {}
|
||||||
|
moreIfaces = True
|
||||||
|
print('Please 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' +
|
||||||
|
'with an active link. (You can only specify one auto device per system, and all subsequent\n'
|
||||||
|
'interface entries will be ignored.)\n')
|
||||||
|
while moreIfaces:
|
||||||
|
ifacein = chkPrompt('Interface device: ', nethelp)
|
||||||
|
addrin = chkPrompt('* Address for {0} in CIDR format (can be an IPv4 or IPv6 address);\n\t' +
|
||||||
|
'use\'auto\' for DHCP/DHCPv6): '.format(ifacein), nethelp)
|
||||||
|
if addrin == 'auto':
|
||||||
|
addrytpe = 'auto'
|
||||||
|
continue
|
||||||
|
else:
|
||||||
|
addrtype = 'static'
|
||||||
|
try:
|
||||||
|
ipver = ipaddress.ip_network(ipaddr, strict = False)
|
||||||
|
except ValueError:
|
||||||
|
exit(' !! ERROR: You did not enter a valid IPv4/IPv6 address.')
|
||||||
|
if addrtype == 'static':
|
||||||
|
|
||||||
|
return(ifaces)
|
||||||
|
|
||||||
conf = {}
|
conf = {}
|
||||||
print('[{0}] Beginning configuration...'.format(datetime.datetime.now()))
|
print('[{0}] Beginning configuration...'.format(datetime.datetime.now()))
|
||||||
print('You may reply with \'wikihelp\' 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).\n')
|
||||||
# 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('\nWhat disk(s) would you like to be configured on the target system?\n' +
|
||||||
'If you have multiple disks, separate with a comma (e.g. \'/dev/sda,/dev/sdb\').\n', diskhelp)
|
'If you have multiple disks, separate with a comma (e.g. \'/dev/sda,/dev/sdb\').\n', diskhelp)
|
||||||
|
# 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'}
|
||||||
conf['disks'] = {}
|
conf['disks'] = {}
|
||||||
for d in diskin.split(','):
|
for d in diskin.split(','):
|
||||||
disk = d.strip()
|
disk = d.strip()
|
||||||
@ -66,10 +107,11 @@ class aifgen(object):
|
|||||||
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\'.')
|
||||||
conf['disks'][disk]['fmt'] = fmt
|
conf['disks'][disk]['fmt'] = fmt
|
||||||
|
conf['disks'][disk]['parts'] = {}
|
||||||
if fmt == 'gpt':
|
if fmt == 'gpt':
|
||||||
maxpart = '256'
|
maxpart = '256'
|
||||||
else:
|
else:
|
||||||
maxpart = '4'
|
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)
|
||||||
if not isinstance(partnumsin, int):
|
if not isinstance(partnumsin, int):
|
||||||
exit(' !! ERROR: Must be an integer.')
|
exit(' !! ERROR: Must be an integer.')
|
||||||
@ -78,16 +120,91 @@ class aifgen(object):
|
|||||||
if partnumsin > int(maxpart):
|
if partnumsin > int(maxpart):
|
||||||
exit(' !! ERROR: Must be less than {0}'.format(maxpart))
|
exit(' !! ERROR: Must be less than {0}'.format(maxpart))
|
||||||
parthelp = diskhelp + ['https://wiki.archlinux.org/index.php/installation_guide#Format_the_partitions',
|
parthelp = diskhelp + ['https://wiki.archlinux.org/index.php/installation_guide#Format_the_partitions',
|
||||||
'https://aif.square-r00t.net/#code_part_code',
|
'https://aif.square-r00t.net/#code_part_code']
|
||||||
'https://aif.square-r00t.net/#fstypes']
|
|
||||||
for partn in range(1, partnumsin + 1):
|
for partn in range(1, partnumsin + 1):
|
||||||
startsize = chkPrompt(('** Where should partition {0} start? Can be percentage [n%] ' +
|
# https://aif.square-r00t.net/#code_part_code
|
||||||
'or size [(+/-)n(K/M/G/T/P)]: ').format(partn), parthelp)
|
conf['disks'][disk]['parts'][partn] = {}
|
||||||
startn = re.sub('[%\-+KMGTP])', '', startsize)
|
for s in ('start', 'stop'):
|
||||||
if int(startn) not in range(0, 100):
|
conf['disks'][disk]['parts'][partn][s] = None
|
||||||
exit()
|
sizein = chkPrompt(('** Where should partition {0} {1}? Can be percentage [n%] ' +
|
||||||
# https://aif.square-r00t.net/#code_part_code
|
'or size [(+/-)n(K/M/G/T/P)]: ').format(partn, s), parthelp)
|
||||||
parthelp.append('https://aif.square-r00t.net/#code_part_code')
|
conf['disks'][disk]['parts'][partn][s] = sizeChk(sizein)
|
||||||
|
newhelp = 'https://aif.square-r00t.net/#fstypes'
|
||||||
|
if newhelp not in parthelp:
|
||||||
|
parthelp.append(newhelp)
|
||||||
|
fstypein = chkPrompt(('** What filesystem type should {0} be? ' +
|
||||||
|
'See wikihelp for valid fstypes: '.format(partn), parthelp))
|
||||||
|
if fstypein not in fstypes.keys():
|
||||||
|
exit(' !! ERROR: {0} is not a valid filesystem type.'.format(fstypein))
|
||||||
|
else:
|
||||||
|
print('\tSelected {0}'.format(fstypes[fstypein]))
|
||||||
|
mnthelp = ['https://wiki.archlinux.org/index.php/installation_guide#Mount_the_file_systems',
|
||||||
|
'https://aif.square-r00t.net/#code_mount_code']
|
||||||
|
mntin = chkPrompt('\nWhat mountpoint(s) would you like to be configured on the target system?\n' +
|
||||||
|
'If you have multiple mountpoints, separate with a comma (e.g. \'/mnt/aif,/mnt/aif/boot\').\n' +
|
||||||
|
'NOTE: Can be \'swap\' for swapspace.', mnthelp)
|
||||||
|
conf['mounts'] = {}
|
||||||
|
for m in mntin.split(','):
|
||||||
|
mount = m.strip()
|
||||||
|
if not re.match('^(/([^/\x00\s]+(/)?)+|swap)$', mount):
|
||||||
|
exit('!! ERROR: Mountpoint {0} does not seem to be a valid path/specifier.'.format(mount))
|
||||||
|
print('\nConfiguring mountpoint {0} ...'.format(mount))
|
||||||
|
dvcin = chkPrompt('* What device/partition should be mounted here? ', mnthelp)
|
||||||
|
if not re.match('^/dev/[A-Za-z0]+', dvcin):
|
||||||
|
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'+
|
||||||
|
'Must be a unique integer (lower numbers mount before higher numbers): ', mnthelp)
|
||||||
|
try:
|
||||||
|
order = int(ordrin)
|
||||||
|
except:
|
||||||
|
exit(' !! ERROR: Must be an integer')
|
||||||
|
if order in conf['mounts'].keys():
|
||||||
|
exit(' !! ERROR: You already have a mountpoint at that order number.')
|
||||||
|
conf['mounts'][order] = {}
|
||||||
|
conf['mounts'][order]['target'] = mount
|
||||||
|
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' +
|
||||||
|
'but may be required for more exotic filesystem types. If you don\'t have to specify one,\n\t' +
|
||||||
|
'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' +
|
||||||
|
'with 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_\.\-]+)*', mntoptsin):
|
||||||
|
exit(' !! ERROR: You seem to have not specified valid mount options.')
|
||||||
|
else:
|
||||||
|
conf['mounts'][order]['opts'] = mntoptsin
|
||||||
|
print('Now, let\'s configure the network. Note that at this time, wireless/more exotic networking is not supported by AIF-NG.')
|
||||||
|
conf['network'] = {}
|
||||||
|
nethelp = ['https://wiki.archlinux.org/index.php/installation_guide#Network_configuration',
|
||||||
|
'https://aif.square-r00t.net/#code_network_code']
|
||||||
|
hostnamein = chkPrompt('What should the newly-installed system\'s hostname be?\n\t' +
|
||||||
|
'It must be in FQDN format, but can be a non-existent domain: ', nethelp)
|
||||||
|
hostname = hostnamein.lower()
|
||||||
|
if len(hostname) > 253:
|
||||||
|
exit(' !! ERROR: A FQDN cannot be more than 253 characters (RFC 1035, 2.3.4)')
|
||||||
|
hostnamelst = hostname.split('.')
|
||||||
|
for c in hostnamelst:
|
||||||
|
if len(c) > 63:
|
||||||
|
exit(' !! ERROR: No component of an FQDN can be more than 63 characters (RFC 1035, 2.3.4)')
|
||||||
|
if not re.match('^[a-zA-Z\d-]{,63}(\.[a-zA-Z\d-]{,63})*', hostname):
|
||||||
|
exit(' !! ERROR: That does not seem to be a valid FQDN.')
|
||||||
|
else:
|
||||||
|
conf['network']['hostname'] = hostname
|
||||||
|
conf['network']['ifaces'] = {}
|
||||||
|
nethelp.append('https://aif.square-r00t.net/#code_iface_code')
|
||||||
|
ifaces = ifacePrompt(nethelp)
|
||||||
|
|
||||||
|
if args['verbose']:
|
||||||
|
import pprint
|
||||||
|
pprint.pprint(conf)
|
||||||
|
return(conf)
|
||||||
|
|
||||||
def validateXML(self):
|
def validateXML(self):
|
||||||
pass
|
pass
|
||||||
@ -100,7 +217,8 @@ class aifgen(object):
|
|||||||
|
|
||||||
def parseArgs():
|
def parseArgs():
|
||||||
args = argparse.ArgumentParser(description = 'AIF-NG Configuration Generator',
|
args = argparse.ArgumentParser(description = 'AIF-NG Configuration Generator',
|
||||||
epilog = 'TIP: this program has context-specific help. e.g. try "%(prog)s create --help"')
|
epilog = 'TIP: this program has context-specific help. e.g. try:\n\t%(prog)s create --help',
|
||||||
|
formatter_class = argparse.RawTextHelpFormatter)
|
||||||
commonargs = argparse.ArgumentParser(add_help = False)
|
commonargs = argparse.ArgumentParser(add_help = False)
|
||||||
commonargs.add_argument('-f',
|
commonargs.add_argument('-f',
|
||||||
'--file',
|
'--file',
|
||||||
@ -118,6 +236,11 @@ def parseArgs():
|
|||||||
viewargs = subparsers.add_parser('view',
|
viewargs = subparsers.add_parser('view',
|
||||||
help = 'View an AIF-NG XML configuration file.',
|
help = 'View an AIF-NG XML configuration file.',
|
||||||
parents = [commonargs])
|
parents = [commonargs])
|
||||||
|
createargs.add_argument('-v',
|
||||||
|
'--verbose',
|
||||||
|
dest = 'verbose',
|
||||||
|
action = 'store_true',
|
||||||
|
help = 'Print the dict of raw values used to create the XML. Mostly/only useful for debugging.')
|
||||||
|
|
||||||
return(args)
|
return(args)
|
||||||
|
|
||||||
@ -170,8 +293,6 @@ def main():
|
|||||||
aif = aifgen(verifyArgs(args))
|
aif = aifgen(verifyArgs(args))
|
||||||
if args['oper'] == 'create':
|
if args['oper'] == 'create':
|
||||||
aif.getOpts()
|
aif.getOpts()
|
||||||
import pprint # DEBUGGING
|
|
||||||
print(args) # DEBUGGING
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
main()
|
main()
|
62
aif-secure.xml
Normal file
62
aif-secure.xml
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8" ?>
|
||||||
|
<aif xmlns:aif="https://aif.square-r00t.net"
|
||||||
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
|
xsi:schemaLocation="https://aif.square-r00t.net aif.xsd">
|
||||||
|
<storage>
|
||||||
|
<disk device="/dev/sda" diskfmt="gpt">
|
||||||
|
<part num="1" start="0%" size="10%" fstype="ef00" />
|
||||||
|
<part num="2" start="10%" size="100%" fstype="8300" />
|
||||||
|
</disk>
|
||||||
|
<mount source="/dev/sda2" target="/mnt/aif" order="1" />
|
||||||
|
<mount source="/dev/sda1" target="/mnt/aif/boot" order="2" />
|
||||||
|
</storage>
|
||||||
|
<network hostname="aiftest.square-r00t.net">
|
||||||
|
<iface device="auto" address="auto" netproto="ipv4" />
|
||||||
|
</network>
|
||||||
|
<system timezone="EST5EDT" locale="en_US.UTF-8" chrootpath="/mnt/aif" reboot="1">
|
||||||
|
<users rootpass="!" />
|
||||||
|
<service name="sshd" status="1" />
|
||||||
|
<service name="cronie" status="1" />
|
||||||
|
<service name="haveged" status="1" />
|
||||||
|
</system>
|
||||||
|
<pacman command="apacman -S">
|
||||||
|
<repos>
|
||||||
|
<repo name="core" enabled="true" siglevel="default" mirror="file:///etc/pacman.d/mirrorlist" />
|
||||||
|
<repo name="extra" enabled="true" siglevel="default" mirror="file:///etc/pacman.d/mirrorlist" />
|
||||||
|
<repo name="community" enabled="true" siglevel="default" mirror="file:///etc/pacman.d/mirrorlist" />
|
||||||
|
<repo name="multilib" enabled="true" siglevel="default" mirror="file:///etc/pacman.d/mirrorlist" />
|
||||||
|
<repo name="testing" enabled="false" siglevel="default" mirror="file:///etc/pacman.d/mirrorlist" />
|
||||||
|
<repo name="multilib-testing" enabled="false" siglevel="default" mirror="file:///etc/pacman.d/mirrorlist" />
|
||||||
|
<repo name="archlinuxfr" enabled="false" siglevel="Optional TrustedOnly" mirror="http://repo.archlinux.fr/$arch" />
|
||||||
|
</repos>
|
||||||
|
<mirrorlist>
|
||||||
|
<mirror>http://mirror.us.leaseweb.net/archlinux/$repo/os/$arch</mirror>
|
||||||
|
<mirror>http://mirrors.advancedhosters.com/archlinux/$repo/os/$arch</mirror>
|
||||||
|
<mirror>http://ftp.osuosl.org/pub/archlinux/$repo/os/$arch</mirror>
|
||||||
|
<mirror>http://arch.mirrors.ionfish.org/$repo/os/$arch</mirror>
|
||||||
|
<mirror>http://mirrors.gigenet.com/archlinux/$repo/os/$arch</mirror>
|
||||||
|
<mirror>http://mirror.jmu.edu/pub/archlinux/$repo/os/$arch</mirror>
|
||||||
|
</mirrorlist>
|
||||||
|
<software>
|
||||||
|
<package name="sed" repo="core" />
|
||||||
|
<package name="python" />
|
||||||
|
<package name="openssh" />
|
||||||
|
<package name="vim" />
|
||||||
|
<package name="vim-plugins" />
|
||||||
|
<package name="haveged" />
|
||||||
|
<package name="byobu" />
|
||||||
|
<package name="etc-update" />
|
||||||
|
<package name="cronie" />
|
||||||
|
<package name="mlocate" />
|
||||||
|
<package name="mtree-git" />
|
||||||
|
</software>
|
||||||
|
</pacman>
|
||||||
|
<bootloader type="grub" target="/boot" efi="true" />
|
||||||
|
<scripts>
|
||||||
|
<script uri="https://aif.square-r00t.net/cfgs/scripts/pkg/python.sh" order="1" execution="pkg" />
|
||||||
|
<script uri="https://aif.square-r00t.net/cfgs/scripts/pkg/apacman.py" order="2" execution="pkg" />
|
||||||
|
<script uri="https://aif.square-r00t.net/cfgs/scripts/post/sshsecure.py" order="1" execution="post" />
|
||||||
|
<script uri="https://aif.square-r00t.net/cfgs/scripts/post/sshkeys.py" order="2" execution="post" />
|
||||||
|
<script uri="https://aif.square-r00t.net/cfgs/scripts/post/configs.py" order="3" execution="post" />
|
||||||
|
</scripts>
|
||||||
|
</aif>
|
Loading…
Reference in New Issue
Block a user