diff --git a/aif-config.py b/aif-config.py new file mode 100755 index 0000000..3800181 --- /dev/null +++ b/aif-config.py @@ -0,0 +1,177 @@ +#!/usr/bin/env python3 + +try: + from lxml import etree + lxml_avail = True +except ImportError: + import xml.etree.ElementTree as etree # https://docs.python.org/3/library/xml.etree.elementtree.html + lxml_avail = False +import argparse +import datetime +import errno +import ipaddress +import os +import pydoc # a dirty hack we use for pagination +import re +import shlex +import socket +import sys +import urllib.request as urlrequest +import urllib.parse as urlparse +import urllib.response as urlresponse +from ftplib import FTP_TLS + +xsd = 'https://aif.square-r00t.net/aif.xsd' + +class aifgen(object): + def __init__(self, args): + self.args = args + + def getXSD(self): + pass + + def getXML(self): + pass + + def getOpts(self): + def chkPrompt(prompt, urls): + txtin = None + txtin = input(prompt) + if txtin in ('wikihelp', ''): + print('\n Articles/pages that you may find helpful for this option are:') + for h in urls: + print(' * {0}'.format(h)) + print() + txtin = input(prompt) + else: + return(txtin) + + conf = {} + print('[{0}] Beginning configuration...'.format(datetime.datetime.now())) + print('You may reply with \'wikihelp\' for the relevant link(s) in the Arch wiki ' + + '(and other resources).\n') + # https://aif.square-r00t.net/#code_disk_code + 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' + + 'If you have multiple disks, separate with a comma (e.g. \'/dev/sda,/dev/sdb\').\n', diskhelp) + conf['disks'] = {} + for d in diskin.split(','): + disk = d.strip() + if not re.match('^/dev/[A-Za-z0]+', disk): + exit('!! ERROR: Disk {0} does not seem to be a valid device path.'.format(disk)) + conf['disks'][disk] = {} + print('\nConfiguring disk {0} ...'.format(disk)) + fmtin = chkPrompt('* What format should this disk use (gpt/bios)? ', diskhelp) + fmt = fmtin.lower() + if fmt not in ('gpt', 'bios'): + exit(' !! ERROR: Must be one of \'gpt\' or \'bios\'.') + conf['disks'][disk]['fmt'] = fmt + if fmt == 'gpt': + maxpart = '256' + else: + maxpart = '4' + partnumsin = chkPrompt('* How many partitions should this disk have? (Maximum: {0}) '.format(maxpart), diskhelp) + if not isinstance(partnumsin, int): + exit(' !! ERROR: Must be an integer.') + if partnumsin < 1: + exit(' !! ERROR: Must be a positive integer.') + if partnumsin > int(maxpart): + exit(' !! ERROR: Must be less than {0}'.format(maxpart)) + 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/#fstypes'] + for partn in range(1, partnumsin + 1): + startsize = chkPrompt(('** Where should partition {0} start? Can be percentage [n%] ' + + 'or size [(+/-)n(K/M/G/T/P)]: ').format(partn), parthelp) + startn = re.sub('[%\-+KMGTP])', '', startsize) + if int(startn) not in range(0, 100): + exit() + # https://aif.square-r00t.net/#code_part_code + parthelp.append('https://aif.square-r00t.net/#code_part_code') + + def validateXML(self): + pass + + def main(self): + if self.args['oper'] == 'create': + self.getOpts() + if self.args['oper'] in ('create', 'view'): + self.validateXML() + +def parseArgs(): + args = argparse.ArgumentParser(description = 'AIF-NG Configuration Generator', + epilog = 'TIP: this program has context-specific help. e.g. try "%(prog)s create --help"') + commonargs = argparse.ArgumentParser(add_help = False) + commonargs.add_argument('-f', + '--file', + dest = 'cfgfile', + help = 'The file to create/validate/view. If not specified, defaults to ./aif.xml', + default = '{0}/aif.xml'.format(os.getcwd())) + subparsers = args.add_subparsers(help = 'Operation to perform', + dest = 'oper') + createargs = subparsers.add_parser('create', + help = 'Create an AIF-NG XML configuration file.', + parents = [commonargs]) + validateargs = subparsers.add_parser('validate', + help = 'Validate an AIF-NG XML configuration file.', + parents = [commonargs]) + viewargs = subparsers.add_parser('view', + help = 'View an AIF-NG XML configuration file.', + parents = [commonargs]) + + return(args) + +def verifyArgs(args): + args['cfgfile'] = os.path.normpath(os.path.abspath(os.path.expanduser(args['cfgfile']))) + args['cfgfile'] = re.sub('^/+', '/', args['cfgfile']) + # Path/file handling - make sure we can create the parent dir if it doesn't exist, + # check that we can write to the file, etc. + if args['oper'] == 'create': + args['cfgbak'] = '{0}.bak.{1}'.format(args['cfgfile'], int(datetime.datetime.utcnow().timestamp())) + try: + temp = True + #mtime = None + #atime = None + if os.path.lexists(args['cfgfile']): + temp = False + #mtime = os.stat(args['cfgfile']).st_mtime + #atime = os.stat(args['cfgfile']).st_atime + os.makedirs(os.path.dirname(args['cfgfile']), exist_ok = True) + with open(args['cfgfile'], 'a') as f: + f.write('') + if temp: + os.remove(args['cfgfile']) + #else: + # WE WERE NEVER HERE. + # I lied; ctime will still be modified, but I think this is playing it safely enough. + # Turns out, though, f.write('') does no modifications but WILL throw the perm error we want. + # Good. + #os.utime(args['cfgfile'], times = (atime, mtime)) + except OSError as e: + print('\nERROR: {0}: {1}'.format(e.strerror, e.filename)) + exit(('\nWe encountered an error when trying to use path {0}.\n' + + 'Please review the output and address any issues present.').format(args['cfgfile'])) + elif args['oper'] == 'view': + try: + with open(args['cfgfile'], 'r') as f: + f.read() + except OSError as e: + print('\nERROR: {0}: {1}'.format(e.strerror, e.filename)) + exit(('\nWe encountered an error when trying to use path {0}.\n' + + 'Please review the output and address any issues present.').format(args['cfgfile'])) + return(args) + +def main(): + args = vars(parseArgs().parse_args()) + if not args['oper']: + parseArgs().print_help() + else: + # verifyArgs(args) + aif = aifgen(verifyArgs(args)) + if args['oper'] == 'create': + aif.getOpts() + import pprint # DEBUGGING + print(args) # DEBUGGING + +if __name__ == '__main__': + main() \ No newline at end of file