basic bootloader support (grub and systemd-boot), script support. untested, as per usual.
This commit is contained in:
207
aifclient.py
207
aifclient.py
@@ -17,6 +17,7 @@ except ImportError:
|
||||
import xml.etree.ElementTree as etree # https://docs.python.org/3/library/xml.etree.elementtree.html
|
||||
lxml_avail = False
|
||||
import shlex
|
||||
import fileinput
|
||||
import os
|
||||
import re
|
||||
import socket
|
||||
@@ -104,6 +105,49 @@ class aif(object):
|
||||
exit('{0} is not a recognised URI type specifier. Must be one of http, https, file, ftp, or ftps.'.format(prefix))
|
||||
return(conf)
|
||||
|
||||
def webFetch(self, uri, auth = False):
|
||||
# Sanitize the user specification and find which protocol to use
|
||||
prefix = uri.split(':')[0].lower()
|
||||
# Use the urllib module
|
||||
if prefix in ('http', 'https', 'file', 'ftp'):
|
||||
if auth:
|
||||
if 'user' in auth.keys() and 'password' in auth.keys():
|
||||
# Set up Basic or Digest auth.
|
||||
passman = urlrequest.HTTPPasswordMgrWithDefaultRealm()
|
||||
if not 'realm' in auth.keys():
|
||||
passman.add_password(None, uri, auth['user'], auth['password'])
|
||||
else:
|
||||
passman.add_password(auth['realm'], uri, auth['user'], auth['password'])
|
||||
if auth['type'] == 'digest':
|
||||
httpauth = urlrequest.HTTPDigestAuthHandler(passman)
|
||||
else:
|
||||
httpauth = urlrequest.HTTPBasicAuthHandler(passman)
|
||||
httpopener = urlrequest.build_opener(httpauth)
|
||||
urlrequest.install_opener(httpopener)
|
||||
with urlrequest.urlopen(uri) as f:
|
||||
data = f.read()
|
||||
elif prefix == 'ftps':
|
||||
if auth:
|
||||
if 'user' in auth.keys():
|
||||
username = auth['user']
|
||||
else:
|
||||
username = 'anonymous'
|
||||
if 'password' in auth.keys():
|
||||
password = auth['password']
|
||||
else:
|
||||
password = 'anonymous'
|
||||
filepath = '/'.join(uri.split('/')[3:])
|
||||
server = uri.split('/')[2]
|
||||
content = StringIO()
|
||||
ftps = FTP_TLS(server)
|
||||
ftps.login(username, password)
|
||||
ftps.prot_p()
|
||||
ftps.retrlines("RETR " + filepath, content.write)
|
||||
data = content.getvalue()
|
||||
else:
|
||||
exit('{0} is not a recognised URI type specifier. Must be one of http, https, file, ftp, or ftps.'.format(prefix))
|
||||
return(data)
|
||||
|
||||
def getXML(self, confobj = False):
|
||||
if not confobj:
|
||||
confobj = self.getConfig()
|
||||
@@ -117,11 +161,13 @@ class aif(object):
|
||||
aifdict = {}
|
||||
for i in ('disk', 'mount', 'network', 'system', 'users', 'software', 'scripts'):
|
||||
aifdict[i] = {}
|
||||
for i in ('network.ifaces', 'system.bootloader', 'system.services', 'users.root', 'scripts.pre', 'scripts.post'):
|
||||
for i in ('network.ifaces', 'system.bootloader', 'system.services', 'users.root'):
|
||||
i = i.split('.')
|
||||
dictname = i[0]
|
||||
keyname = i[1]
|
||||
aifdict[dictname][keyname] = {}
|
||||
aifdict['scripts']['pre'] = False
|
||||
aifdict['scripts']['post'] = False
|
||||
aifdict['users']['root']['password'] = False
|
||||
for i in ('repos', 'mirrors', 'packages'):
|
||||
aifdict['software'][i] = {}
|
||||
@@ -278,6 +324,11 @@ class aif(object):
|
||||
# The bootloader setup...
|
||||
for x in xmlobj.find('bootloader').attrib:
|
||||
aifdict['system']['bootloader'][x] = xmlobj.find('bootloader').attrib[x]
|
||||
# The script setup...
|
||||
for x in xmlobj.find('scripts'):
|
||||
scripttype =
|
||||
if not aifdict['scripts'][scripttype]:
|
||||
aifdict['scripts'][scripttype] = {}
|
||||
return(aifdict)
|
||||
|
||||
class archInstall(object):
|
||||
@@ -442,6 +493,7 @@ class archInstall(object):
|
||||
hostscript.append(['timedatectl', 'set-ntp', 'true'])
|
||||
hostscript.append(['pacstrap', self.system['chrootpath'], 'base'])
|
||||
with open('{0}/etc/fstab'.format(self.system['chrootpath']), 'a') as f:
|
||||
f.write('# Generated by AIF-NG.\n')
|
||||
f.write(chrootfstab.decode('utf-8'))
|
||||
# Validating this would be better with pytz, but it's not stdlib. dateutil would also work, but same problem.
|
||||
# https://stackoverflow.com/questions/15453917/get-all-available-timezones
|
||||
@@ -476,20 +528,24 @@ class archInstall(object):
|
||||
if v.startswith('#{0}'.format(x)):
|
||||
localeraw[i] = x + '\n'
|
||||
with open('{0}/etc/locale.gen'.format(self.system['chrootpath']), 'w') as f:
|
||||
f.write('# Modified by AIF-NG.\n')
|
||||
f.write(''.join(localeraw))
|
||||
with open('{0}/etc/locale.conf', 'a') as f:
|
||||
f.write('# Added by AIF-NG.\n')
|
||||
f.write('LANG={0}\n'.format(locale[0].split()[0]))
|
||||
chrootcmds.append(['locale-gen'])
|
||||
# Set up the kbd layout.
|
||||
# Currently there is NO validation on this. TODO.
|
||||
if self.system['kbd']:
|
||||
with open('{0}/etc/vconsole.conf'.format(self.system['chrootpath']), 'a') as f:
|
||||
f.write('KEYMAP={0}\n'.format(self.system['kbd']))
|
||||
f.write('# Generated by AIF-NG.\nKEYMAP={0}\n'.format(self.system['kbd']))
|
||||
# Set up the hostname.
|
||||
with open('{0}/etc/hostname'.format(self.system['chrootpath']), 'w') as f:
|
||||
f.write('# Generated by AIF-NG.\n')
|
||||
f.write(self.network['hostname'] + '\n')
|
||||
with open('{0}/etc/hosts'.format(self.system['chrootpath']), 'a') as f:
|
||||
f.write('127.0.0.1\t{0}\t{1}\n'.format(self.network['hostname'], (self.network['hostname']).split('.')[0]))
|
||||
f.write('# Added by AIF-NG.\n127.0.0.1\t{0}\t{1}\n'.format(self.network['hostname'],
|
||||
(self.network['hostname']).split('.')[0]))
|
||||
# Set up networking.
|
||||
ifaces = []
|
||||
# Ideally we'd find a better way to do... all of this. Patches welcome. TODO.
|
||||
@@ -553,8 +609,10 @@ class archInstall(object):
|
||||
filename = '{0}/etc/netctl/{1}'.format(self.system['chrootpath'], ifacedev)
|
||||
# The good news is since it's a clean install, we only have to account for our own data, not pre-existing.
|
||||
with open(filename, 'w') as f:
|
||||
f.write('# Generated by AIF-NG.\n')
|
||||
f.write(netprofile)
|
||||
with open('{0}/etc/systemd/system/netctl@{1}.service'.format(self.system['chrootpath'], ifacedev)) as f:
|
||||
f.write('# Generated by AIF-NG.\n')
|
||||
f.write(('.include /usr/lib/systemd/system/netctl@.service\n\n[Unit]\n' +
|
||||
'Description=A basic {0} ethernet connection\n' +
|
||||
'BindsTo=sys-subsystem-net-devices-{1}.device\n' +
|
||||
@@ -563,30 +621,152 @@ class archInstall(object):
|
||||
'{0}/etc/systemd/system/multi-user.target.wants/netctl@{1}.service'.format(self.system['chrootpath'], ifacedev))
|
||||
os.symlink('/usr/lib/systemd/system/netctl.service',
|
||||
'{0}/etc/systemd/system/multi-user.target.wants/netctl.service'.format(self.system['chrootpath']))
|
||||
# Root password
|
||||
if self.users['root']['password']:
|
||||
roothash = self.users['root']['password']
|
||||
else:
|
||||
roothash = '!'
|
||||
with fileinput.input('{0}/etc/shadow'.format(self.system['chrootpath']), inplace = True) as f:
|
||||
for line in f:
|
||||
linelst = line.split(':')
|
||||
if linelst[0] == 'root':
|
||||
linelst[1] = roothash
|
||||
print(':'.join(linelst), end = '')
|
||||
# Add users
|
||||
for user in self.users.keys():
|
||||
# We already handled root user
|
||||
if user != 'root':
|
||||
cmd = ['useradd']
|
||||
if self.users[user]['home']['create']:
|
||||
cmd.append('-m')
|
||||
if self.users[user]['home']['path']:
|
||||
cmd.append('-d {0}'.format(self.users[user]['home']['path']))
|
||||
if self.users[user]['comment']:
|
||||
cmd.append('-c "{0}"'.format(self.users[user]['comment']))
|
||||
if self.users[user]['gid']:
|
||||
cmd.append('-g {0}'.format(self.users[user]['gid']))
|
||||
if self.users[user]['uid']:
|
||||
cmd.append('-u {0}'.format(self.users[user]['uid']))
|
||||
if self.users[user]['password']:
|
||||
cmd.append('-p "{0}"'.format(self.users[user]['password']))
|
||||
cmd.append(user)
|
||||
chrootcmds.append(cmd)
|
||||
# Add groups
|
||||
if self.users[user]['xgroup']:
|
||||
for group in self.users[user]['xgroup'].keys():
|
||||
gcmd = False
|
||||
if self.users[user]['xgroup'][group]['create']:
|
||||
gcmd = ['groupadd']
|
||||
if self.users[user]['xgroup'][group]['gid']:
|
||||
gcmd.append('-g {0}'.format(self.users[user]['xgroup'][group]['gid']))
|
||||
gcmd.append(group)
|
||||
chrootcmds.append(gcmd)
|
||||
chrootcmds.append(['usermod', '-aG', '{0}'.format(','.join(self.users[user]['xgroup'].keys())), user])
|
||||
# Handle sudo
|
||||
if self.users[user]['sudo']:
|
||||
os.makedirs('{0}/etc/sudoers.d'.format(self.system['chrootpath']), exist_ok = True)
|
||||
os.chmod('{0}/etc/sudoers.d'.format(self.system['chrootpath']), 0o750)
|
||||
with open('{0}/etc/sudoers.d/{1}'.format(self.system['chrootpath'], user), 'w') as f:
|
||||
f.write('# Generated by AIF-NG.\nDefaults:{0} !lecture\n{0} ALL=(ALL) ALL\n'.format(user))
|
||||
# Base configuration- initcpio, etc.
|
||||
chrootcmds.append(['mkinitcpio', '-p', 'linux'])
|
||||
# Run the basic host prep
|
||||
with open(os.devnull, 'w') as DEVNULL:
|
||||
for c in hostscript:
|
||||
subprocess.call(c, stdout = DEVNULL, stderr = subprocess.STDOUT)
|
||||
return(chrootcmds)
|
||||
|
||||
def chroot(self, chrootcmds = False):
|
||||
def bootloader(self):
|
||||
# Bootloader configuration
|
||||
btldr = self.system['bootloader']['type']
|
||||
bootcmds = []
|
||||
chrootpath = self.system['chrootpath']
|
||||
bttarget = self.system['bootloader']['target']
|
||||
if btldr == 'grub':
|
||||
bootcmds.append(['grub-install'])
|
||||
if self.system['bootloader']['efi']:
|
||||
bootcmds[0].extend(['--target=x86_64-efi', '--efi-directory={0}'.format(bttarget), '--bootloader-id="Arch Linux"'])
|
||||
else:
|
||||
bootcmds[0].extend(['--target=i386-pc', bttarget])
|
||||
bootcmds.append(['grub-mkconfig', '-o', '/{0}/grub/grub.cfg'.format(chrootpath, bttarget)])
|
||||
if btldr == 'systemd':
|
||||
if self.system['bootloader']['target'] != '/boot':
|
||||
shutil.copy2('{0}/boot/vmlinuz-linux'.format(chrootpath),
|
||||
'{0}/{1}/vmlinuz-linux'.format(chrootpath, bttarget))
|
||||
shutil.copy2('{0}/boot/initramfs-linux.img'.format(chrootpath),
|
||||
'{0}/{1}/initramfs-linux.img'.format(chrootpath, bttarget))
|
||||
with open('{0}/{1}/loader/loader.conf'.format(chrootpath, bttarget), 'w') as f:
|
||||
f.write('# Generated by AIF-NG.\ndefault arch\ntimeout 4\neditor 0\n')
|
||||
# Gorram, I wish there was a better way to get the partition UUID in stdlib.
|
||||
majmindev = os.lstat('{0}/{1}'.format(chrootpath, bttarget)).st_dev
|
||||
majdev = os.major(majmindev)
|
||||
mindev = os.minor(majmindev)
|
||||
btdev = os.path.basename(os.readlink('/sys/dev/block/{0}:{1}'.format(majdev, mindev)))
|
||||
partuuid = False
|
||||
for d in os.listdir('/dev/disk/by-uuid'):
|
||||
linktarget = os.path.basename(os.readlink(d))
|
||||
if linktarget == btdev:
|
||||
partuuid = linktarget
|
||||
break
|
||||
if not partuuid:
|
||||
exit('ERROR: Cannot determine PARTUUID for /dev/{0}.'.format(btdev))
|
||||
with open('{0}/{1}/loader/entries/arch.conf'.format(chrootpath, bttarget)) as f:
|
||||
f.write(('# Generated by AIF-NG.\ntitle\t\tArch Linux\nlinux /vmlinuz-linux\n') +
|
||||
('initrd /initramfs-linux.img\noptions root=PARTUUID={0} rw\n').format(partuuid))
|
||||
bootcmds.append(['bootctl', '--path={0}', 'install'])
|
||||
return(bootcmds)
|
||||
|
||||
def scriptcmds(self):
|
||||
if xmlobj.find('scripts') is not None:
|
||||
self.scripts['pre'] = []
|
||||
self.scripts['post'] = []
|
||||
tempscriptdict = {'pre': {}, 'post': {}}
|
||||
for x in xmlobj.find('scripts'):
|
||||
if all(keyname in list(x.attrib.keys()) for keyname in ('user', 'password')):
|
||||
auth = {}
|
||||
auth['user'] = x.attrib['user']
|
||||
auth['password'] = x.attrib['password']
|
||||
if 'realm' in x.attrib.keys():
|
||||
auth['realm'] = x.attrib['realm']
|
||||
if 'authtype' in x.attrib.keys():
|
||||
auth['type'] = x.attrib['authtype']
|
||||
scriptcontents = self.webFetch(x.attrib['uri']).decode('utf-8')
|
||||
else:
|
||||
scriptcontents = self.webFetch(x.attrib['uri']).decode('utf-8')
|
||||
if x.attrib['bootstrap'].lower() in ('true', '1'):
|
||||
tempscriptdict['pre'][x.attrib['order']] = scriptcontents
|
||||
else:
|
||||
tempscriptdict['post'][x.attrib['order']] = scriptcontents
|
||||
for d in ('pre', 'post'):
|
||||
keylst = list(tempscriptdict[d].keys())
|
||||
keylst.sort()
|
||||
for s in keylst:
|
||||
aifdict['scripts'][d].append(tempscriptdict[d][s])
|
||||
|
||||
def chroot(self, chrootcmds = False, bootcmds = False):
|
||||
if not chrootcmds:
|
||||
chrootcmds = self.setup()
|
||||
chrootscript = """#!/bin/bash
|
||||
# https://aif.square-r00t.net/
|
||||
|
||||
"""
|
||||
with open('{0}/root/aif.sh'.format(self.system['chrootpath']), 'w') as f:
|
||||
f.write(chrootscript)
|
||||
os.chmod('{0}/root/aif.sh'.format(self.system['chrootpath']), 0o700)
|
||||
if not bootcmds:
|
||||
bootcmds = self.bootloader()
|
||||
# We don't need this currently, but we might down the road.
|
||||
#chrootscript = '#!/bin/bash\n# https://aif.square-r00t.net/\n\n'
|
||||
#with open('{0}/root/aif.sh'.format(self.system['chrootpath']), 'w') as f:
|
||||
# f.write(chrootscript)
|
||||
#os.chmod('{0}/root/aif.sh'.format(self.system['chrootpath']), 0o700)
|
||||
with open('{0}/root/aif-pre.sh'.format(self.system['chrootpath']), 'w') as f:
|
||||
f.write(self.scripts['pre'])
|
||||
with open('{0}/root/aif-post.sh'.format(self.system['chrootpath']), 'w') as f:
|
||||
f.write(self.scripts['post'])
|
||||
real_root = os.open("/", os.O_RDONLY)
|
||||
os.chroot(self.system['chrootpath'])
|
||||
# Does this even work with an os.chroot()? Let's hope so!
|
||||
with open(os.devnull, 'w') as DEVNULL:
|
||||
for c in chrootcmds:
|
||||
subprocess.call(c, stdout = DEVNULL, stderr = subprocess.STDOUT)
|
||||
os.system('{0}/root/aif.sh'.format(self.system['chrootpath']))
|
||||
for b in bootcmds:
|
||||
subprocess.call(b, stdout = DEVNULL, stderr = subprocess.STDOUT)
|
||||
os.system('{0}/root/aif-pre.sh'.format(self.system['chrootpath']))
|
||||
#os.system('{0}/root/aif.sh'.format(self.system['chrootpath']))
|
||||
os.system('{0}/root/aif-post.sh'.format(self.system['chrootpath']))
|
||||
os.fchdir(real_root)
|
||||
os.chroot('.')
|
||||
@@ -602,8 +782,7 @@ def runInstall(confdict):
|
||||
install = archInstall(confdict)
|
||||
#install.format()
|
||||
#install.mounts()
|
||||
##chrootcmds = install.setup()
|
||||
##install.chroot(chrootcmds)
|
||||
#install.bootloader()
|
||||
#install.chroot()
|
||||
#install.unmount()
|
||||
|
||||
|
||||
Reference in New Issue
Block a user