2017-03-20 18:11:29 -04:00
#!/usr/bin/env python3
2017-03-21 15:13:15 -04:00
## REQUIRES: ##
2017-03-26 02:54:38 -04:00
# parted #
# sgdisk ### (yes, both)
2017-03-21 15:13:15 -04:00
# python 3 with standard library
# (OPTIONAL) lxml
# pacman in the host environment
# arch-install-scripts: https://www.archlinux.org/packages/extra/any/arch-install-scripts/
# a network connection
# the proper kernel arguments.
2017-03-20 18:11:29 -04:00
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 shlex
2017-04-26 04:55:34 -04:00
import fileinput
2017-03-20 18:11:29 -04:00
import os
import re
2017-04-23 03:11:27 -04:00
import socket
2017-03-20 18:11:29 -04:00
import subprocess
2017-04-23 03:11:27 -04:00
import ipaddress
import copy
2017-03-20 18:11:29 -04:00
import pprint
import urllib . request as urlrequest
import urllib . parse as urlparse
import urllib . response as urlresponse
from ftplib import FTP_TLS
from io import StringIO
class aif ( object ) :
def __init__ ( self ) :
pass
def kernelargs ( self ) :
if ' DEBUG ' in os . environ . keys ( ) :
kernelparamsfile = ' /tmp/cmdline '
else :
kernelparamsfile = ' /proc/cmdline '
args = { }
args [ ' aif ' ] = False
# For FTP or HTTP auth
args [ ' aif_user ' ] = False
args [ ' aif_password ' ] = False
args [ ' aif_auth ' ] = False
args [ ' aif_realm ' ] = False
args [ ' aif_auth ' ] = ' basic '
with open ( kernelparamsfile , ' r ' ) as f :
cmdline = f . read ( )
for p in shlex . split ( cmdline ) :
if p . startswith ( ' aif ' ) :
param = p . split ( ' = ' )
if len ( param ) == 1 :
param . append ( True )
args [ param [ 0 ] ] = param [ 1 ]
if not args [ ' aif ' ] :
exit ( ' You do not have AIF enabled. Exiting. ' )
args [ ' aif_auth ' ] = args [ ' aif_auth ' ] . lower ( )
return ( args )
def getConfig ( self , args = False ) :
if not args :
args = self . kernelargs ( )
# Sanitize the user specification and find which protocol to use
prefix = args [ ' aif_url ' ] . split ( ' : ' ) [ 0 ] . lower ( )
# Use the urllib module
if prefix in ( ' http ' , ' https ' , ' file ' , ' ftp ' ) :
if args [ ' aif_user ' ] and args [ ' aif_password ' ] :
# Set up Basic or Digest auth.
passman = urlrequest . HTTPPasswordMgrWithDefaultRealm ( )
if not args [ ' aif_realm ' ] :
passman . add_password ( None , args [ ' aif_url ' ] , args [ ' aif_user ' ] , args [ ' aif_password ' ] )
else :
passman . add_password ( args [ ' aif_realm ' ] , args [ ' aif_url ' ] , args [ ' aif_user ' ] , args [ ' aif_password ' ] )
if args [ ' aif_auth ' ] == ' digest ' :
httpauth = urlrequest . HTTPDigestAuthHandler ( passman )
else :
httpauth = urlrequest . HTTPBasicAuthHandler ( passman )
httpopener = urlrequest . build_opener ( httpauth )
urlrequest . install_opener ( httpopener )
with urlrequest . urlopen ( args [ ' aif_url ' ] ) as f :
conf = f . read ( )
elif prefix == ' ftps ' :
if args [ ' aif_user ' ] :
username = args [ ' aif_user ' ]
else :
username = ' anonymous '
if args [ ' aif_password ' ] :
password = args [ ' aif_password ' ]
else :
password = ' anonymous '
filepath = ' / ' . join ( args [ ' aif_url ' ] . split ( ' / ' ) [ 3 : ] )
server = args [ ' aif_url ' ] . split ( ' / ' ) [ 2 ]
content = StringIO ( )
ftps = FTP_TLS ( server )
ftps . login ( username , password )
ftps . prot_p ( )
ftps . retrlines ( " RETR " + filepath , content . write )
conf = 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 ( conf )
2017-04-26 04:55:34 -04:00
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 )
2017-03-20 18:11:29 -04:00
def getXML ( self , confobj = False ) :
if not confobj :
confobj = self . getConfig ( )
xmlobj = etree . fromstring ( confobj )
return ( xmlobj )
def buildDict ( self , xmlobj = False ) :
if not xmlobj :
xmlobj = self . getXML ( )
# Set up the skeleton dicts
aifdict = { }
for i in ( ' disk ' , ' mount ' , ' network ' , ' system ' , ' users ' , ' software ' , ' scripts ' ) :
aifdict [ i ] = { }
2017-04-26 04:55:34 -04:00
for i in ( ' network.ifaces ' , ' system.bootloader ' , ' system.services ' , ' users.root ' ) :
2017-03-20 18:11:29 -04:00
i = i . split ( ' . ' )
dictname = i [ 0 ]
keyname = i [ 1 ]
aifdict [ dictname ] [ keyname ] = { }
2017-04-26 04:55:34 -04:00
aifdict [ ' scripts ' ] [ ' pre ' ] = False
aifdict [ ' scripts ' ] [ ' post ' ] = False
2017-03-20 18:11:29 -04:00
aifdict [ ' users ' ] [ ' root ' ] [ ' password ' ] = False
for i in ( ' repos ' , ' mirrors ' , ' packages ' ) :
aifdict [ ' software ' ] [ i ] = { }
# Set up the dict elements for disk partitioning
for i in xmlobj . findall ( ' storage/disk ' ) :
disk = i . attrib [ ' device ' ]
fmt = i . attrib [ ' diskfmt ' ] . lower ( )
if not fmt in ( ' gpt ' , ' bios ' ) :
exit ( ' Device {0} \' s format " {1} " is not a valid type (one of gpt, bios). ' . format ( disk ,
fmt ) )
aifdict [ ' disk ' ] [ disk ] = { }
aifdict [ ' disk ' ] [ disk ] [ ' fmt ' ] = fmt
aifdict [ ' disk ' ] [ disk ] [ ' parts ' ] = { }
for x in i :
if x . tag == ' part ' :
partnum = x . attrib [ ' num ' ]
aifdict [ ' disk ' ] [ disk ] [ ' parts ' ] [ partnum ] = { }
for a in x . attrib :
aifdict [ ' disk ' ] [ disk ] [ ' parts ' ] [ partnum ] [ a ] = x . attrib [ a ]
# Set up mountpoint dicts
for i in xmlobj . findall ( ' storage/mount ' ) :
device = i . attrib [ ' source ' ]
mntpt = i . attrib [ ' target ' ]
order = i . attrib [ ' order ' ]
if ' fstype ' in i . keys ( ) :
fstype = i . attrib [ ' fstype ' ]
else :
fstype = None
if ' opts ' in i . keys ( ) :
opts = i . attrib [ ' opts ' ]
else :
opts = None
aifdict [ ' mount ' ] [ order ] = { }
aifdict [ ' mount ' ] [ order ] [ ' device ' ] = device
aifdict [ ' mount ' ] [ order ] [ ' mountpt ' ] = mntpt
aifdict [ ' mount ' ] [ order ] [ ' fstype ' ] = fstype
aifdict [ ' mount ' ] [ order ] [ ' opts ' ] = opts
# Set up networking dicts
aifdict [ ' network ' ] [ ' hostname ' ] = xmlobj . find ( ' network ' ) . attrib [ ' hostname ' ]
for i in xmlobj . findall ( ' network/iface ' ) :
# Create a dict for the iface name.
iface = i . attrib [ ' device ' ]
proto = i . attrib [ ' netproto ' ]
address = i . attrib [ ' address ' ]
2017-04-23 03:11:27 -04:00
if ' gateway ' in i . attrib . keys ( ) :
gateway = i . attrib [ ' gateway ' ]
else :
gateway = False
if ' resolvers ' in i . attrib . keys ( ) :
resolvers = i . attrib [ ' resolvers ' ]
else :
resolvers = False
2017-03-20 18:11:29 -04:00
if iface not in aifdict [ ' network ' ] [ ' ifaces ' ] . keys ( ) :
aifdict [ ' network ' ] [ ' ifaces ' ] [ iface ] = { }
if proto not in aifdict [ ' network ' ] [ ' ifaces ' ] [ iface ] . keys ( ) :
2017-04-23 03:11:27 -04:00
aifdict [ ' network ' ] [ ' ifaces ' ] [ iface ] [ proto ] = { }
if ' gw ' not in aifdict [ ' network ' ] [ ' ifaces ' ] [ iface ] [ proto ] . keys ( ) :
aifdict [ ' network ' ] [ ' ifaces ' ] [ iface ] [ proto ] [ ' gw ' ] = gateway
aifdict [ ' network ' ] [ ' ifaces ' ] [ iface ] [ proto ] [ ' addresses ' ] = [ ]
aifdict [ ' network ' ] [ ' ifaces ' ] [ iface ] [ proto ] [ ' addresses ' ] . append ( address )
aifdict [ ' network ' ] [ ' ifaces ' ] [ iface ] [ ' resolvers ' ] = [ ]
if resolvers :
for ip in filter ( None , re . split ( ' [, \ s]+ ' , resolvers ) ) :
if ip not in aifdict [ ' network ' ] [ ' ifaces ' ] [ iface ] [ ' resolvers ' ] :
aifdict [ ' network ' ] [ ' ifaces ' ] [ iface ] [ ' resolvers ' ] . append ( ip )
else :
aifdict [ ' network ' ] [ ' ifaces ' ] [ iface ] [ proto ] [ ' resolvers ' ] = False
2017-03-20 18:11:29 -04:00
# Set up the users dicts
aifdict [ ' users ' ] [ ' root ' ] [ ' password ' ] = xmlobj . find ( ' system/users ' ) . attrib [ ' rootpass ' ]
for i in xmlobj . findall ( ' system/users ' ) :
for x in i :
username = x . attrib [ ' name ' ]
aifdict [ ' users ' ] [ username ] = { }
for a in ( ' uid ' , ' group ' , ' gid ' , ' password ' , ' comment ' , ' sudo ' ) :
if a in x . attrib . keys ( ) :
aifdict [ ' users ' ] [ username ] [ a ] = x . attrib [ a ]
else :
aifdict [ ' users ' ] [ username ] [ a ] = None
sudo = ( x . attrib [ ' sudo ' ] ) . lower ( ) in ( ' true ' , ' 1 ' )
aifdict [ ' users ' ] [ username ] [ ' sudo ' ] = sudo
# And we also need to handle the homedir and xgroup situation
for n in ( ' home ' , ' xgroup ' ) :
aifdict [ ' users ' ] [ username ] [ n ] = False
for a in x :
if not aifdict [ ' users ' ] [ username ] [ a . tag ] :
aifdict [ ' users ' ] [ username ] [ a . tag ] = { }
for b in a . attrib :
if a . tag == ' xgroup ' :
if b == ' name ' :
groupname = a . attrib [ b ]
if groupname not in aifdict [ ' users ' ] [ username ] [ ' xgroup ' ] . keys ( ) :
aifdict [ ' users ' ] [ username ] [ ' xgroup ' ] [ a . attrib [ b ] ] = { }
else :
aifdict [ ' users ' ] [ username ] [ ' xgroup ' ] [ a . attrib [ ' name ' ] ] [ b ] = a . attrib [ b ]
else :
aifdict [ ' users ' ] [ username ] [ a . tag ] [ b ] = a . attrib [ b ]
# And fill in any missing values. We could probably use the XSD and use of defaults to do this, but... oh well.
if isinstance ( aifdict [ ' users ' ] [ username ] [ ' xgroup ' ] , dict ) :
for g in aifdict [ ' users ' ] [ username ] [ ' xgroup ' ] . keys ( ) :
for k in ( ' create ' , ' gid ' ) :
if k not in aifdict [ ' users ' ] [ username ] [ ' xgroup ' ] [ g ] . keys ( ) :
aifdict [ ' users ' ] [ username ] [ ' xgroup ' ] [ g ] [ k ] = False
elif k == ' create ' :
aifdict [ ' users ' ] [ username ] [ ' xgroup ' ] [ g ] [ k ] = aifdict [ ' users ' ] [ username ] [ ' xgroup ' ] [ g ] [ k ] . lower ( ) in ( ' true ' , ' 1 ' )
if isinstance ( aifdict [ ' users ' ] [ username ] [ ' home ' ] , dict ) :
for k in ( ' path ' , ' create ' ) :
if k not in aifdict [ ' users ' ] [ username ] [ ' home ' ] . keys ( ) :
aifdict [ ' users ' ] [ username ] [ ' home ' ] [ k ] = False
elif k == ' create ' :
aifdict [ ' users ' ] [ username ] [ ' home ' ] [ k ] = aifdict [ ' users ' ] [ username ] [ ' home ' ] [ k ] . lower ( ) in ( ' true ' , ' 1 ' )
# Set up the system settings, if applicable.
aifdict [ ' system ' ] [ ' timezone ' ] = False
aifdict [ ' system ' ] [ ' locale ' ] = False
aifdict [ ' system ' ] [ ' kbd ' ] = False
2017-03-21 15:13:15 -04:00
aifdict [ ' system ' ] [ ' chrootpath ' ] = False
for i in ( ' locale ' , ' timezone ' , ' kbd ' , ' chrootpath ' ) :
2017-03-20 18:11:29 -04:00
if i in xmlobj . find ( ' system ' ) . attrib :
aifdict [ ' system ' ] [ i ] = xmlobj . find ( ' system ' ) . attrib [ i ]
# And now services...
if xmlobj . find ( ' system/service ' ) is None :
aifdict [ ' system ' ] [ ' services ' ] = False
else :
for x in xmlobj . findall ( ' system/service ' ) :
svcname = x . attrib [ ' name ' ]
state = x . attrib [ ' status ' ] . lower ( ) in ( ' true ' , ' 1 ' )
aifdict [ ' system ' ] [ ' services ' ] [ svcname ] = { }
aifdict [ ' system ' ] [ ' services ' ] [ svcname ] [ ' status ' ] = state
# And software. First the mirror list.
if xmlobj . find ( ' pacman/mirrorlist ' ) is None :
aifdict [ ' software ' ] [ ' mirrors ' ] = False
else :
aifdict [ ' software ' ] [ ' mirrors ' ] = [ ]
for x in xmlobj . findall ( ' pacman/mirrorlist ' ) :
for i in x :
aifdict [ ' software ' ] [ ' mirrors ' ] . append ( i . text )
# And then the repo list.
for x in xmlobj . find ( ' pacman/repos ' ) :
repo = x . attrib [ ' name ' ]
aifdict [ ' software ' ] [ ' repos ' ] [ repo ] = { }
aifdict [ ' software ' ] [ ' repos ' ] [ repo ] [ ' enabled ' ] = x . attrib [ ' enabled ' ] . lower ( ) in ( ' true ' , ' 1 ' )
aifdict [ ' software ' ] [ ' repos ' ] [ repo ] [ ' siglevel ' ] = x . attrib [ ' siglevel ' ]
aifdict [ ' software ' ] [ ' repos ' ] [ repo ] [ ' mirror ' ] = x . attrib [ ' mirror ' ]
# And packages.
if xmlobj . find ( ' pacman/software ' ) is None :
aifdict [ ' software ' ] [ ' packages ' ] = False
else :
aifdict [ ' software ' ] [ ' packages ' ] = { }
for x in xmlobj . findall ( ' pacman/software/package ' ) :
aifdict [ ' software ' ] [ ' packages ' ] [ x . attrib [ ' name ' ] ] = { }
if ' repo ' in x . attrib :
aifdict [ ' software ' ] [ ' packages ' ] [ x . attrib [ ' name ' ] ] [ ' repo ' ] = x . attrib [ ' repo ' ]
else :
aifdict [ ' software ' ] [ ' packages ' ] [ x . attrib [ ' name ' ] ] [ ' repo ' ] = None
# The bootloader setup...
for x in xmlobj . find ( ' bootloader ' ) . attrib :
aifdict [ ' system ' ] [ ' bootloader ' ] [ x ] = xmlobj . find ( ' bootloader ' ) . attrib [ x ]
2017-04-26 04:55:34 -04:00
# The script setup...
2017-05-04 02:59:12 -04:00
# We do this later on.
# for x in xmlobj.find('scripts'):
# if x.attrib['bootstrap'].lower() in ('true', '1'):
# scripttype = 'pre'
# else:
# scripttype = 'post'
# if not aifdict['scripts'][scripttype]:
# aifdict['scripts'][scripttype] = {}
# aifdict['scripts'][scripttype][int(x.attrib['order'])] = {}
2017-05-04 03:10:19 -04:00
if xmlobj . find ( ' scripts ' ) is not None :
aifdict [ ' scripts ' ] [ ' pre ' ] = [ ]
aifdict [ ' 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 ' ] , auth ) . 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 ] )
2017-03-20 18:11:29 -04:00
return ( aifdict )
class archInstall ( object ) :
def __init__ ( self , aifdict ) :
for k , v in aifdict . items ( ) :
setattr ( self , k , v )
def format ( self ) :
# 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 ' }
# We want to build a mapping of commands to run after partitioning. This will be fleshed out in the future to hopefully include more.
formatting = { }
# TODO: we might want to provide a way to let users specify extra options here.
# TODO: label support?
formatting [ ' ef00 ' ] = [ ' mkfs.vfat ' , ' -F ' , ' 32 ' , ' % PART % ' ]
formatting [ ' ef01 ' ] = formatting [ ' ef00 ' ]
formatting [ ' ef02 ' ] = formatting [ ' ef00 ' ]
formatting [ ' 8200 ' ] = [ ' mkswap ' , ' -c ' , ' % PART % ' ]
formatting [ ' 8300 ' ] = [ ' mkfs.ext4 ' , ' -c ' , ' -q ' , ' % PART % ' ] # some people are DEFINITELY not going to be happy about this. we need to figure out a better way to customize this.
for fs in ( ' 8301 ' , ' 8302 ' , ' 8303 ' , ' 8304 ' , ' 8305 ' , ' 8306 ' , ' 8307 ' ) :
formatting [ fs ] = formatting [ ' 8300 ' ]
#formatting['8e00'] = FOO # TODO: LVM configuration
#formatting['fd00'] = FOO # TODO: MDADM configuration
cmds = [ ]
for d in self . disk :
partnums = [ int ( x ) for x in self . disk [ d ] [ ' parts ' ] . keys ( ) ]
partnums . sort ( )
cmds . append ( [ ' sgdisk ' , ' -Z ' , d ] )
if self . disk [ d ] [ ' fmt ' ] == ' gpt ' :
diskfmt = ' gpt '
2017-05-04 04:15:44 -04:00
if len ( partnums ) > = 129 or partnums [ - 1 ] > = 129 :
2017-03-20 18:11:29 -04:00
exit ( ' GPT only supports 128 partitions (and partition allocations). ' )
cmds . append ( [ ' sgdisk ' , ' -og ' , d ] )
elif self . disk [ d ] [ ' fmt ' ] == ' bios ' :
diskfmt = ' msdos '
cmds . append ( [ ' sgdisk ' , ' -om ' , d ] )
cmds . append ( [ ' parted ' , d , ' --script ' , ' -a ' , ' optimal ' ] )
with open ( os . devnull , ' w ' ) as DEVNULL :
for c in cmds :
subprocess . call ( c , stdout = DEVNULL , stderr = subprocess . STDOUT )
cmds = [ ]
disksize = { }
disksize [ ' start ' ] = subprocess . check_output ( [ ' sgdisk ' , ' -F ' , d ] )
disksize [ ' max ' ] = subprocess . check_output ( [ ' sgdisk ' , ' -E ' , d ] )
for p in partnums :
# Need to do some mathz to get the actual sectors if we're using percentages.
for s in ( ' start ' , ' size ' ) :
val = self . disk [ d ] [ ' parts ' ] [ str ( p ) ] [ s ]
if ' % ' in val :
stripped = val . replace ( ' % ' , ' ' )
modifier = re . sub ( ' [0-9]+ % ' , ' ' , val )
percent = re . sub ( ' (-| \ +)* ' , ' ' , stripped )
2017-04-23 03:11:27 -04:00
decimal = float ( percent ) / float ( 100 )
newval = int ( float ( disksize [ ' max ' ] ) * decimal )
2017-03-20 18:11:29 -04:00
if s == ' start ' :
newval = newval + int ( disksize [ ' start ' ] )
self . disk [ d ] [ ' parts ' ] [ str ( p ) ] [ s ] = modifier + str ( newval )
if self . disk [ d ] [ ' fmt ' ] == ' gpt ' :
for p in partnums :
size = { }
size [ ' start ' ] = self . disk [ d ] [ ' parts ' ] [ str ( p ) ] [ ' start ' ]
size [ ' end ' ] = self . disk [ d ] [ ' parts ' ] [ str ( p ) ] [ ' size ' ]
fstype = self . disk [ d ] [ ' parts ' ] [ str ( p ) ] [ ' fstype ' ] . lower ( )
if fstype not in fstypes . keys ( ) :
print ( ' Filesystem type {0} is not valid. Must be a code from: \n CODE:FILESYSTEM ' . format ( fstype ) )
for k , v in fstypes . items ( ) :
print ( k + " : " + v )
exit ( )
cmds . append ( [ ' sgdisk ' ,
' -n ' , ' {0} : {1} : {2} ' . format ( str ( p ) ,
self . disk [ d ] [ ' parts ' ] [ str ( p ) ] [ ' start ' ] ,
self . disk [ d ] [ ' parts ' ] [ str ( p ) ] [ ' size ' ] ) ,
#'-c', '{0}:"{1}"'.format(str(p), self.disk[d]['parts'][str(p)]['label']), # TODO: add support for partition labels
' -t ' , ' {0} : {1} ' . format ( str ( p ) , fstype ) ,
d ] )
mkformat = formatting [ fstype ]
for x , y in enumerate ( mkformat ) :
if y == ' % PART % ' :
mkformat [ x ] = d + str ( p )
cmds . append ( mkformat )
2017-05-04 04:35:23 -04:00
# TODO: add non-gpt stuff here?
2017-03-20 18:11:29 -04:00
with open ( os . devnull , ' w ' ) as DEVNULL :
for p in cmds :
subprocess . call ( p , stdout = DEVNULL , stderr = subprocess . STDOUT )
2017-05-04 04:35:23 -04:00
usermntidx = self . mount . keys ( )
2017-03-20 18:11:29 -04:00
2017-03-21 15:13:15 -04:00
def mounts ( self ) :
mntorder = list ( self . mount . keys ( ) )
mntorder . sort ( )
for m in mntorder :
mnt = self . mount [ m ]
if mnt [ ' mountpt ' ] . lower ( ) == ' swap ' :
cmd = [ ' swapon ' , mnt [ ' device ' ] ]
else :
cmd = [ ' mount ' , mnt [ ' device ' ] , mnt [ ' mountpt ' ] ]
if mnt [ ' opts ' ] :
cmd . insert ( 1 , ' -o {0} ' . format ( mnt [ ' opts ' ] ) )
if mnt [ ' fstype ' ] :
cmd . insert ( 1 , ' -t {0} ' . format ( mnt [ ' fstype ' ] ) )
# with open(os.devnull, 'w') as DEVNULL:
# for p in cmd:
# subprocess.call(p, stdout = DEVNULL, stderr = subprocess.STDOUT)
# And we need to add some extra mounts to support a chroot. We also need to know what was mounted before.
with open ( ' /proc/mounts ' , ' r ' ) as f :
procmounts = f . read ( )
mountlist = { }
for i in procmounts . splitlines ( ) :
mountlist [ i . split ( ) [ 1 ] ] = i
cmounts = { }
for m in ( ' chroot ' , ' resolv ' , ' proc ' , ' sys ' , ' efi ' , ' dev ' , ' pts ' , ' shm ' , ' run ' , ' tmp ' ) :
cmounts [ m ] = None
chrootdir = self . system [ ' chrootpath ' ]
# chroot (bind mount... onto itself. it's so stupid, i know. see https://bugs.archlinux.org/task/46169)
if chrootdir not in mountlist . keys ( ) :
cmounts [ ' chroot ' ] = [ ' mount ' , ' --bind ' , chrootdir , chrootdir ]
# resolv.conf (for DNS resolution in the chroot)
if ( chrootdir + ' /etc/resolv.conf ' ) not in mountlist . keys ( ) :
cmounts [ ' resolv ' ] = [ ' /bin/mount ' , ' --bind ' , ' -o ' , ' ro ' , ' /etc/resolv.conf ' , chrootdir + ' /etc/resolv.conf ' ]
# proc
if ( chrootdir + ' /proc ' ) not in mountlist . keys ( ) :
cmounts [ ' proc ' ] = [ ' /bin/mount ' , ' -t ' , ' proc ' , ' -o ' , ' nosuid,noexec,nodev ' , ' proc ' , chrootdir + ' /proc ' ]
# sys
if ( chrootdir + ' /sys ' ) not in mountlist . keys ( ) :
cmounts [ ' sys ' ] = [ ' /bin/mount ' , ' -t ' , ' sysfs ' , ' -o ' , ' nosuid,noexec,nodev,ro ' , ' sys ' , chrootdir + ' /sys ' ]
# efi (if it exists on the host)
if ' /sys/firmware/efi/efivars ' in mountlist . keys ( ) :
if ( chrootdir + ' /sys/firmware/efi/efivars ' ) not in mountlist . keys ( ) :
cmounts [ ' efi ' ] = [ ' /bin/mount ' , ' -t ' , ' efivarfs ' , ' -o ' , ' nosuid,noexec,nodev ' , ' efivarfs ' , chrootdir + ' /sys/firmware/efi/efivars ' ]
# dev
if ( chrootdir + ' /dev ' ) not in mountlist . keys ( ) :
cmounts [ ' dev ' ] = [ ' /bin/mount ' , ' -t ' , ' devtmpfs ' , ' -o ' , ' mode=0755,nosuid ' , ' udev ' , chrootdir + ' /dev ' ]
# pts
if ( chrootdir + ' /dev/pts ' ) not in mountlist . keys ( ) :
cmounts [ ' pts ' ] = [ ' /bin/mount ' , ' -t ' , ' devpts ' , ' -o ' , ' mode=0620,gid=5,nosuid,noexec ' , ' devpts ' , chrootdir + ' /dev/pts ' ]
# shm (if it exists on the host)
if ' /dev/shm ' in mountlist . keys ( ) :
if ( chrootdir + ' /dev/shm ' ) not in mountlist . keys ( ) :
cmounts [ ' shm ' ] = [ ' /bin/mount ' , ' -t ' , ' tmpfs ' , ' -o ' , ' mode=1777,nosuid,nodev ' , ' shm ' , chrootdir + ' /dev/shm ' ]
# run (if it exists on the host)
if ' /run ' in mountlist . keys ( ) :
if ( chrootdir + ' /run ' ) not in mountlist . keys ( ) :
cmounts [ ' run ' ] = [ ' /bin/mount ' , ' -t ' , ' tmpfs ' , ' -o ' , ' nosuid,nodev,mode=0755 ' , ' run ' , chrootdir + ' /run ' ]
# tmp (if it exists on the host)
if ' /tmp ' in mountlist . keys ( ) :
if ( chrootdir + ' /tmp ' ) not in mountlist . keys ( ) :
cmounts [ ' tmp ' ] = [ ' /bin/mount ' , ' -t ' , ' tmpfs ' , ' -o ' , ' mode=1777,strictatime,nodev,nosuid ' , ' tmp ' , chrootdir + ' /tmp ' ]
2017-05-04 04:35:23 -04:00
# Because the order of these mountpoints is so ridiculously important, we hardcode it.
# Yeah, python 3.6 has ordered dicts, but do we really want to risk it?
2017-03-21 15:13:15 -04:00
with open ( os . devnull , ' w ' ) as DEVNULL :
for m in ( ' chroot ' , ' resolv ' , ' proc ' , ' sys ' , ' efi ' , ' dev ' , ' pts ' , ' shm ' , ' run ' , ' tmp ' ) :
if cmounts [ m ] :
subprocess . call ( cmounts [ m ] , stdout = DEVNULL , stderr = subprocess . STDOUT )
# Okay. So we finally have all the mounts bound. Whew.
2017-05-04 04:35:23 -04:00
return ( )
2017-03-21 15:13:15 -04:00
def setup ( self ) :
# TODO: could we leverage https://github.com/hartwork/image-bootstrap somehow? I want to keep this close
# to standard Python libs, though, to reduce dependency requirements.
hostscript = [ ]
chrootcmds = [ ]
locales = [ ]
locale = [ ]
# Get the necessary fstab additions for the guest
chrootfstab = subprocess . check_output ( [ ' genfstab ' , ' -U ' , self . system [ ' chrootpath ' ] ] )
# Set up the time, and then kickstart the guest install.
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 :
2017-04-26 04:55:34 -04:00
f . write ( ' # Generated by AIF-NG. \n ' )
2017-03-21 15:13:15 -04:00
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
tzlist = subprocess . check_output ( [ ' timedatectl ' , ' list-timezones ' ] ) . decode ( ' utf-8 ' ) . splitlines ( )
if self . system [ ' timezone ' ] not in tzlist :
print ( ' WARNING (non-fatal): {0} does not seem to be a valid timezone, but we \' re continuing anyways. ' . format ( self . system [ ' timezone ' ] ) )
os . symlink ( ' /usr/share/zoneinfo/ {0} ' . format ( self . system [ ' timezone ' ] ) ,
' {0} /etc/localtime ' . format ( self . system [ ' chrootpath ' ] ) )
# This is an ugly hack. TODO: find a better way of determining if the host is set to UTC in the RTC. maybe the datetime module can do it.
utccheck = subprocess . check_output ( [ ' timedatectl ' , ' status ' ] ) . decode ( ' utf-8 ' ) . splitlines ( )
utccheck = [ x . strip ( ' ' ) for x in utccheck ]
for i , v in enumerate ( utccheck ) :
if v . startswith ( ' RTC in local ' ) :
utcstatus = ( v . split ( ' : ' ) [ 1 ] ) . lower ( ) in ( ' yes ' )
break
if utcstatus :
chrootcmds . append ( [ ' hwclock ' , ' --systohc ' ] )
# We need to check the locale, and set up locale.gen.
with open ( ' {0} /etc/locale.gen ' . format ( self . system [ ' chrootpath ' ] ) , ' r ' ) as f :
localeraw = f . readlines ( )
for line in localeraw :
if not line . startswith ( ' # ' ) : # Comments, thankfully, have a space between the leading octothorpe and the comment. Locales have no space.
i = line . strip ( ) . strip ( ' # ' )
if i != ' ' : # We also don't want blank entries. Keep it clean, folks.
locales . append ( i )
for i in locales :
localelst = i . split ( )
if localelst [ 0 ] . lower ( ) . startswith ( self . system [ ' locale ' ] . lower ( ) ) :
locale . append ( ' ' . join ( localelst ) . strip ( ) )
for i , v in enumerate ( localeraw ) :
for x in locale :
if v . startswith ( ' # {0} ' . format ( x ) ) :
localeraw [ i ] = x + ' \n '
with open ( ' {0} /etc/locale.gen ' . format ( self . system [ ' chrootpath ' ] ) , ' w ' ) as f :
2017-04-26 04:55:34 -04:00
f . write ( ' # Modified by AIF-NG. \n ' )
2017-03-21 15:13:15 -04:00
f . write ( ' ' . join ( localeraw ) )
with open ( ' {0} /etc/locale.conf ' , ' a ' ) as f :
2017-04-26 04:55:34 -04:00
f . write ( ' # Added by AIF-NG. \n ' )
2017-03-21 15:13:15 -04:00
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 :
2017-04-26 04:55:34 -04:00
f . write ( ' # Generated by AIF-NG. \n KEYMAP= {0} \n ' . format ( self . system [ ' kbd ' ] ) )
2017-03-21 15:13:15 -04:00
# Set up the hostname.
with open ( ' {0} /etc/hostname ' . format ( self . system [ ' chrootpath ' ] ) , ' w ' ) as f :
2017-04-26 04:55:34 -04:00
f . write ( ' # Generated by AIF-NG. \n ' )
2017-03-21 15:13:15 -04:00
f . write ( self . network [ ' hostname ' ] + ' \n ' )
with open ( ' {0} /etc/hosts ' . format ( self . system [ ' chrootpath ' ] ) , ' a ' ) as f :
2017-04-26 04:55:34 -04:00
f . write ( ' # Added by AIF-NG. \n 127.0.0.1 \t {0} \t {1} \n ' . format ( self . network [ ' hostname ' ] ,
( self . network [ ' hostname ' ] ) . split ( ' . ' ) [ 0 ] ) )
2017-03-26 02:55:15 -04:00
# Set up networking.
2017-04-23 03:11:27 -04:00
ifaces = [ ]
# Ideally we'd find a better way to do... all of this. Patches welcome. TODO.
if ' auto ' in self . network [ ' ifaces ' ] . keys ( ) :
# Get the default route interface.
for line in subprocess . check_output ( [ ' ip ' , ' -oneline ' , ' route ' , ' show ' ] ) . decode ( ' utf-8 ' ) . splitlines ( ) :
line = line . split ( )
if line [ 0 ] == ' default ' :
autoiface = line [ 4 ]
break
ifaces = list ( self . network [ ' ifaces ' ] . keys ( ) )
ifaces . sort ( )
if autoiface in ifaces :
ifaces . remove ( autoiface )
for iface in ifaces :
resolvers = False
if ' resolvers ' in self . network [ ' ifaces ' ] [ iface ] . keys ( ) :
resolvers = self . network [ ' ifaces ' ] [ iface ] [ ' resolvers ' ]
if iface == ' auto ' :
ifacedev = autoiface
iftype = ' dhcp '
else :
ifacedev = iface
iftype = ' static '
netprofile = ' Description= \' A basic {0} ethernet connection ( {1} ) \' \n Interface= {1} \n Connection=ethernet \n ' . format ( iftype , ifacedev )
if ' ipv4 ' in self . network [ ' ifaces ' ] [ iface ] . keys ( ) :
if self . network [ ' ifaces ' ] [ iface ] [ ' ipv4 ' ] :
netprofile + = ' IP= {0} \n ' . format ( iftype )
if ' ipv6 ' in self . network [ ' ifaces ' ] [ iface ] . keys ( ) :
if self . network [ ' ifaces ' ] [ iface ] [ ' ipv6 ' ] :
netprofile + = ' IP6= {0} \n ' . format ( iftype ) # TODO: change this to stateless if iftype='dhcp' instead?
for proto in ( ' ipv4 ' , ' ipv6 ' ) :
addrs = [ ]
if proto in self . network [ ' ifaces ' ] [ iface ] . keys ( ) :
if proto == ' ipv4 ' :
addr = ' Address '
gwstring = ' Gateway '
elif proto == ' ipv6 ' :
addr = ' Address6 '
gwstring = ' Gateway6 '
gw = self . network [ ' ifaces ' ] [ iface ] [ proto ] [ ' gw ' ]
for ip in self . network [ ' ifaces ' ] [ iface ] [ proto ] [ ' addresses ' ] :
if ip == ' auto ' :
continue
else :
try :
ipver = ipaddress . ip_network ( ip , strict = False )
addrs . append ( ip )
except ValueError :
exit ( ' {0} was specified but is NOT a valid IPv4/IPv6 address! ' . format ( ip ) )
if iftype == ' static ' :
# Static addresses
netprofile + = ' {0} =( \' {1} \' ) \n ' . format ( addr , ( ' \' \' ' ) . join ( addrs ) )
# Gateway
if gw :
netprofile + = ' {0} = {1} \n ' . format ( gwstring , gw )
# DNS resolvers
if resolvers :
netprofile + = ' DNS=( \' {0} \' ) \n ' . format ( ' \' \' ' . join ( resolvers ) )
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 :
2017-04-26 04:55:34 -04:00
f . write ( ' # Generated by AIF-NG. \n ' )
2017-04-23 03:11:27 -04:00
f . write ( netprofile )
with open ( ' {0} /etc/systemd/system/netctl@ {1} .service ' . format ( self . system [ ' chrootpath ' ] , ifacedev ) ) as f :
2017-04-26 04:55:34 -04:00
f . write ( ' # Generated by AIF-NG. \n ' )
2017-04-23 03:11:27 -04:00
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 ' +
' After=sys-subsystem-net-devices- {1} .device \n ' ) . format ( iftype , ifacedev ) )
os . symlink ( ' /etc/systemd/system/netctl@ {0} .service ' . format ( ifacedev ) ,
' {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 ' ] ) )
2017-04-26 04:55:34 -04:00
# 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. \n Defaults: {0} !lecture \n {0} ALL=(ALL) ALL \n ' . format ( user ) )
2017-04-23 03:11:27 -04:00
# Base configuration- initcpio, etc.
2017-03-21 15:13:15 -04:00
chrootcmds . append ( [ ' mkinitcpio ' , ' -p ' , ' linux ' ] )
2017-04-26 04:55:34 -04:00
# Run the basic host prep
2017-03-21 15:13:15 -04:00
with open ( os . devnull , ' w ' ) as DEVNULL :
for c in hostscript :
subprocess . call ( c , stdout = DEVNULL , stderr = subprocess . STDOUT )
return ( chrootcmds )
2017-04-26 04:55:34 -04:00
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. \n default arch \n timeout 4 \n editor 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. \n title \t \t Arch Linux \n linux /vmlinuz-linux \n ' ) +
( ' initrd /initramfs-linux.img \n options root=PARTUUID= {0} rw \n ' ) . format ( partuuid ) )
bootcmds . append ( [ ' bootctl ' , ' --path= {0} ' , ' install ' ] )
2017-05-04 04:15:44 -04:00
# TODO: Add a bit here to alter EFI boot order so we boot right to the newly-installed env.
# should probably be optional.
2017-04-26 04:55:34 -04:00
return ( bootcmds )
2017-05-04 04:15:44 -04:00
def scriptcmds ( self , scripttype ) :
# Pre-run/"booststrap" scripts
t = scripttype
if t in self . scripts . keys ( ) :
for i , s in enumerate ( self . scripts [ t ] ) :
if t == ' post ' :
dirpath = ' {0} /scripts/ {1} ' . format ( self . system [ ' chrootpath ' ] , t )
2017-04-27 05:08:39 -04:00
else :
2017-05-04 04:15:44 -04:00
dirpath = ' /root/scripts/ {0} ' . format ( t )
os . makedirs ( dirpath , exist_ok = True )
filepath = ' {0} / {1} ' . format ( dirpath , i )
with open ( filepath , ' w ' ) as f :
f . write ( s )
os . chmod ( filepath , 0o700 )
os . chown ( filepath , 0 , 0 ) # shouldn't be necessary, but just in case the umask's messed up or something.
if t == ' pre ' :
# We want to run these right away.
with open ( os . devnull , ' w ' ) as DEVNULL :
for i , s in enumerate ( self . scripts [ ' pre ' ] ) :
2017-05-04 04:35:23 -04:00
subprocess . call ( ' /root/scripts/pre/ {0} ' . format ( i ) ,
2017-05-04 04:15:44 -04:00
stdout = DEVNULL ,
2017-05-04 04:35:23 -04:00
stderr = subprocess . STDOUT )
2017-05-04 04:15:44 -04:00
return ( )
2017-04-27 05:08:39 -04:00
def packagecmds ( self ) :
pass
2017-04-26 04:55:34 -04:00
2017-04-27 05:08:39 -04:00
def chroot ( self , chrootcmds = False , bootcmds = False , scriptcmds = False ) :
2017-03-21 15:13:15 -04:00
if not chrootcmds :
chrootcmds = self . setup ( )
2017-04-26 04:55:34 -04:00
if not bootcmds :
bootcmds = self . bootloader ( )
2017-04-27 05:08:39 -04:00
if not scriptcmds :
scriptcmds = self . scripts
2017-04-26 04:55:34 -04:00
# 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)
2017-03-21 15:13:15 -04:00
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 )
2017-04-26 04:55:34 -04:00
for b in bootcmds :
subprocess . call ( b , stdout = DEVNULL , stderr = subprocess . STDOUT )
2017-04-27 05:08:39 -04:00
if scriptcmds [ ' post ' ] :
2017-05-04 04:15:44 -04:00
for i , s in enumerate ( scriptcmds [ ' post ' ] ) :
subprocess . call ( ' /root/scripts/post/ {0} ' . format ( i ) ,
stdout = DEVNULL ,
stderr = subproces . STDOUT )
2017-04-27 05:08:39 -04:00
#os.system('{0}/root/aif-pre.sh'.format(self.system['chrootpath']))
#os.system('{0}/root/aif-post.sh'.format(self.system['chrootpath']))
2017-03-21 15:13:15 -04:00
os . fchdir ( real_root )
os . chroot ( ' . ' )
os . close ( real_root )
if not os . path . isfile ( ' {0} /sbin/init ' . format ( chrootdir ) ) :
os . symlink ( ' ../lib/systemd/systemd ' , ' {0} /sbin/init ' . format ( chrootdir ) )
def unmount ( self ) :
with open ( os . devnull , ' w ' ) as DEVNULL :
subprocess . call ( [ ' unmount ' , ' -lR ' , self . system [ ' chrootpath ' ] ] , stdout = DEVNULL , stderr = subprocess . STDOUT )
2017-03-20 18:11:29 -04:00
def runInstall ( confdict ) :
install = archInstall ( confdict )
2017-05-04 04:15:44 -04:00
install . scriptcmds ( ' pre ' )
install . format ( )
install . mounts ( )
2017-05-04 04:35:23 -04:00
install . chroot ( )
install . scriptcmds ( ' post ' )
install . unmount ( )
2017-03-20 18:11:29 -04:00
def main ( ) :
if os . getuid ( ) != 0 :
exit ( ' This must be run as root. ' )
conf = aif ( )
instconf = conf . buildDict ( )
2017-05-04 04:15:44 -04:00
if ' DEBUG ' in os . environ . keys ( ) :
import pprint
pprint . pprint ( instconf )
2017-03-20 18:11:29 -04:00
runInstall ( instconf )
if __name__ == " __main__ " :
2017-03-21 15:13:15 -04:00
main ( )