# # kickstart.py: kickstart install support # # Copyright 1999-2004 Red Hat, Inc. # # This software may be freely redistributed under the terms of the GNU # library public license. # # You should have received a copy of the GNU Library Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. # import iutil import isys import os from installclass import BaseInstallClass from partitioning import * from autopart import * from fsset import * from flags import flags from constants import * import sys import raid import string import partRequests import urllib2 import lvm from rhpl.translate import _ from rhpl.log import log KS_MISSING_PROMPT = 0 KS_MISSING_IGNORE = 1 class KickstartError(Exception): def __init__(self, val = ""): self.value = val def __str__ (self): return self.value class KickstartValueError(KickstartError): def __init__(self, val = ""): self.value = val def __str__ (self): return self.value class KSAppendException(KickstartError): def __init__(self, s=""): self.str = s def __str__(self): return self.str class Script: def __repr__(self): str = ("(s: '%s' i: %s c: %d)") % \ (self.script, self.interp, self.inChroot) return string.replace(str, "\n", "|") def __init__(self, script, interp, inChroot, logfile = None): self.script = script self.interp = interp self.inChroot = inChroot self.logfile = logfile def run(self, chroot, serial): scriptRoot = "/" if self.inChroot: scriptRoot = chroot path = scriptRoot + "/tmp/ks-script" f = open(path, "w") f.write(self.script) f.close() os.chmod(path, 0700) if self.logfile is not None: messages = self.logfile elif serial: messages = "/tmp/ks-script.log" else: messages = "/dev/tty3" rc = iutil.execWithRedirect(self.interp, [self.interp,"/tmp/ks-script"], stdout = messages, stderr = messages, root = scriptRoot) if rc != 0: log("WARNING - Error code %s encountered running a kickstart %%pre/%%post script", rc) os.unlink(path) class KickstartBase(BaseInstallClass): name = "kickstart" def postAction(self, rootPath, serial): log("Running kickstart %%post script(s)") for script in self.postScripts: script.run(rootPath, serial) log("All kickstart %%post script(s) have been run") def doRootPw(self, id, args): (args, extra) = isys.getopt(args, '', [ 'iscrypted' ]) isCrypted = 0 for n in args: (str, arg) = n if (str == '--iscrypted'): isCrypted = 1 if len(extra) != 1: raise KickstartValueError, "a single argument is expected to rootPw" self.setRootPassword(id, extra[0], isCrypted = isCrypted) self.skipSteps.append("accounts") def doFirewall(self, id, args): (args, extra) = isys.getopt(args, '', [ 'dhcp', 'ssh', 'telnet', 'smtp', 'http', 'ftp', 'enabled', 'enable', 'port=', 'high', 'medium', 'disabled', 'disable', 'trust=' ]) enable = -1 trusts = [] ports = [] for n in args: (str, arg) = n if str == '--ssh': ports.append("22:tcp") elif str == '--telnet': ports.append("23:tcp") elif str == '--smtp': ports.append("25:tcp") elif str == '--http': ports.extend(["80:tcp", "443:tcp"]) elif str == '--ftp': ports.append("21:tcp") elif str == '--high' or str == '--medium': log("used deprecated firewall option: %s" %(str[2:],)) enable = 1 elif str == '--enabled' or str == "--enable": enable = 1 elif str == '--disabled' or str == "--disable": enable = 0 elif str == '--trust': trusts.append(arg) elif str == '--port': theports = arg.split(",") for p in theports: p = p.strip() if p.find(":") == -1: p = "%s:tcp" %(p,) ports.append(p) self.setFirewall(id, enable, trusts, ports) def doSELinux(self, id, args): (args, extra) = isys.getopt(args, '', [ 'disabled', 'enforcing', 'permissive' ] ) sel = 2 for n in args: (str, arg) = n if str == "--disabled": sel = 0 elif str == "--permissive": sel = 1 elif str == "--enforcing": sel = 2 self.setSELinux(id, sel) def doZFCP(self, id, args): (args, extra) = isys.getopt(args, '', ["devnum", "scsiid", "wwpn", "scsilun", "fcplun"]) devnum = None scsiid = None wwpn = None scsilun = None fcplun = None for n in args: (str, arg) = n if str == "--devnum": devnum = id.zfcp.sanitizeDeviceInput(arg) elif str == "--scsid": scsiid = id.zfcp.sanitizeHexInput(arg) elif str == "--wwpn": wwpn = id.zfcp.sanitizeHexInput(arg) elif str == "--scsilun": scsilun = id.zfcp.sanitizeHexInput(arg) elif str == "--fcplun": fcplun = id.zfcp.sanitizeFCPLInput(arg) if id.zfcp.checkValidDevice(devnum) == -1: raise KickstartValueError, "Invalid devnum specified" if id.zfcp.checkValidID(scsiid) == -1: raise KickstartValueError, "Invalid scsiid specified" if id.zfcp.checkValid64BitHex(wwpn) == -1: raise KickstartValueError, "Invalid wwpn specified" if id.zfcp.checkValidID(scsilun) == -1: raise KickstartValueError, "Invalid scsilun specified" if id.zfcp.checkValid64BitHex(fcplun) == -1: raise KickstartValueError, "Invalid fcplun specified" if ((devnum is None) or (scsiid is None) or (wwpn is None) or (scsilun is None) or (fcplun is None)): raise KickstartError, "ZFCP config must specify all of devnum, scsiid, wwpn, scsilun, and fcplun" self.setZFCP(id, devnum, scsiid, wwpn, scsilun, fcplun) self.skipSteps.append("zfcpconfig") def doAuthconfig(self, id, args): (args, extra) = isys.getopt(args, '', [ 'useshadow', 'enableshadow', 'enablemd5', 'enablenis', 'nisdomain=', 'nisserver=', 'enableldap', 'enableldapauth', 'ldapserver=', 'ldapbasedn=', 'enableldaptls', 'enablekrb5', 'krb5realm=', 'krb5kdc=', 'krb5adminserver=', 'enablehesiod', 'hesiodlhs=', 'hesiodrhs=', 'enablesmbauth', 'smbservers=', 'smbworkgroup=', 'enablecache']) useShadow = 0 useMd5 = 0 useNis = 0 nisServer = "" nisDomain = "" nisBroadcast = 0 useLdap = 0 useLdapauth = 0 useLdaptls = 0 ldapServer = "" ldapBasedn = "" useKrb5 = 0 krb5Realm = "" krb5Kdc = "" krb5Admin = "" useHesiod = 0 hesiodLhs = "" hesiodRhs = "" useSamba = 0 smbServers = "" smbWorkgroup = "" enableCache = 0 for n in args: (str, arg) = n if (str == '--enablenis'): useNis = 1 elif (str == '--useshadow') or (str == '--enableshadow'): useShadow = 1 elif (str == '--enablemd5'): useMd5 = 1 elif (str == '--nisserver'): nisServer = arg elif (str == '--nisdomain'): nisDomain = arg elif (str == '--enableldap'): useLdap = 1 elif (str == '--enableldapauth'): useLdapauth = 1 elif (str == '--ldapserver'): ldapServer = arg elif (str == '--ldapbasedn'): ldapBasedn = arg elif (str == '--enableldaptls'): useLdaptls = 1 elif (str == '--enablekrb5'): useKrb5 = 1 elif (str == '--krb5realm'): krb5Realm = arg elif (str == '--krb5kdc'): krb5Kdc = arg elif (str == '--krb5adminserver'): krb5Admin = arg elif (str == '--enablehesiod'): useHesiod = 1 elif (str == '--hesiodlhs'): hesiodLhs = arg elif (str == '--hesiodrhs'): hesiodRhs = arg elif (str == '--enablesmbauth'): useSamba = 1 elif (str == '--smbservers'): smbServers = arg elif (str == '--smbworkgroup'): smbWorkgroup = arg elif (str == '--enablecache'): enableCache = 1 if useNis and not nisServer: nisBroadcast = 1 self.setAuthentication(id, useShadow, useMd5, useNis, nisDomain, nisBroadcast, nisServer, useLdap, useLdapauth, ldapServer, ldapBasedn, useLdaptls, useKrb5, krb5Realm, krb5Kdc, krb5Admin, useHesiod, hesiodLhs, hesiodRhs, useSamba, smbServers, smbWorkgroup, enableCache) self.skipSteps.append("authentication") def doBootloader (self, id, args, useLilo = 0): (args, extra) = isys.getopt(args, '', [ 'append=', 'location=', 'useLilo', 'lba32', 'password=', 'md5pass=', 'linear', 'nolinear', 'upgrade', 'driveorder=']) validLocations = [ "mbr", "partition", "none", "boot" ] appendLine = "" location = "mbr" password = None md5pass = None forceLBA = 0 linear = 1 upgrade = 0 driveorder = [] for n in args: (str, arg) = n if str == '--append': appendLine = arg elif str == '--location': location = arg elif str == '--useLilo': # log("used deprecated option --useLilo, ignoring") useLilo = 1 elif str == '--linear': linear = 1 elif str == '--nolinear': linear = 0 elif str == '--lba32': forceLBA = 1 elif str == '--password': password = arg elif str == '--md5pass': md5pass = arg elif str == '--upgrade': upgrade = 1 elif str == '--driveorder': driveorder = string.split(arg, ',') if location not in validLocations: raise KickstartValueError, "mbr, partition, or none expected for bootloader command" if location == "none": location = None elif location == "partition": location = "boot" if upgrade and not id.upgrade.get(): raise KickstartError, "Selected upgrade mode for bootloader but not doing an upgrade" if upgrade: id.bootloader.kickstart = 1 id.bootloader.doUpgradeOnly = 1 self.showSteps.append("bootloadersetup") self.setBootloader(id, useLilo, location, linear, forceLBA, password, md5pass, appendLine, driveorder) self.skipSteps.append("upgbootloader") self.skipSteps.append("bootloader") self.skipSteps.append("bootloaderadvanced") if location is None: self.skipSteps.append("instbootloader") def doLilo (self, id, args): self.doBootloader(id, args, useLilo = 1) def doFirstboot(self, id, args): (args, extra) = isys.getopt(args, '', ['reconfig', 'enable', 'enabled', 'disable', 'disabled']) fb = FIRSTBOOT_SKIP for n in args: (str, arg) = n if str == '--reconfig': fb = FIRSTBOOT_RECONFIG elif str == '--enable' or str == "--enabled": fb = FIRSTBOOT_DEFAULT elif str == '--disable' or str == "--disabled": fb = FIRSTBOOT_SKIP id.firstboot = fb def doLiloCheck (self, id, args): drives = isys.hardDriveDict ().keys() drives.sort(isys.compareDrives) device = drives[0] isys.makeDevInode(device, '/tmp/' + device) fd = os.open('/tmp/' + device, os.O_RDONLY) os.unlink('/tmp/' + device) block = os.read(fd, 512) os.close(fd) if block[6:10] == "LILO": sys.exit(0) def doTimezone(self, id, args): (args, extra) = isys.getopt(args, '', [ 'utc' ]) isUtc = 0 for n in args: (str, arg) = n if str == '--utc': isUtc = 1 self.setTimezoneInfo(id, extra[0], asUtc = isUtc) self.skipSteps.append("timezone") def doXconfig(self, id, args): (args, extra) = isys.getopt(args, '', [ 'server=', 'card=', 'videoram=', 'monitor=', 'hsync=', 'vsync=', 'resolution=', 'depth=', 'startxonboot', 'noprobe', 'defaultdesktop=' ]) if extra: raise KickstartValueError, "unexpected arguments to xconfig command" server = None card = None videoRam = None monitor = None hsync = None vsync = None resolution = None depth = None noProbe = 0 startX = 0 defaultdesktop = "" for n in args: (str, arg) = n if (str == "--noprobe"): noProbe = 1 elif (str == "--server"): server = arg elif (str == "--card"): card = arg elif (str == "--videoram"): videoRam = arg elif (str == "--monitor"): monitor = arg elif (str == "--hsync"): hsync = arg elif (str == "--vsync"): vsync = arg elif (str == "--resolution"): resolution = arg elif (str == "--depth"): depth = string.atoi(arg) elif (str == "--startxonboot"): startX = 1 elif (str == "--defaultdesktop"): defaultdesktop = arg self.configureX(id, server, card, videoRam, monitor, hsync, vsync, resolution, depth, noProbe, startX) self.setDesktop(id, defaultdesktop) self.skipSteps.append("videocard") self.skipSteps.append("monitor") self.skipSteps.append("xcustom") self.skipSteps.append("handleX11pkgs") self.skipSteps.append("checkmonitorok") self.skipSteps.append("setsanex") def doMonitor(self, id, args): (args, extra) = isys.getopt(args, '', [ 'monitor=', 'hsync=', 'vsync=' ]) if extra: raise KickstartValueError, "unexpected arguments to monitor command" monitor = None hsync = None vsync = None for n in args: (str, arg) = n if (str == "--monitor"): monitor = arg elif (str == "--hsync"): hsync = arg elif (str == "--vsync"): vsync = arg self.skipSteps.append("monitor") self.skipSteps.append("checkmonitorok") self.setMonitor(id, hsync = hsync, vsync = vsync, monitorName = monitor) def doUpgrade(self, id, args): self.installType = "upgrade" id.upgrade.set(1) def doNetwork(self, id, args): # nodns is only used by the loader (args, extra) = isys.getopt(args, '', [ 'bootproto=', 'ip=', 'netmask=', 'gateway=', 'nameserver=', 'nodns', 'device=', 'hostname=', 'ethtool=', 'onboot=', 'dhcpclass=', 'essid=', 'wepkey=']) bootProto = "dhcp" ip = None netmask = "" gateway = "" nameserver = "" hostname = "" ethtool = "" essid = "" wepkey = "" onboot = 1 device = None dhcpclass = None for n in args: (str, arg) = n if str == "--bootproto": bootProto = arg elif str == "--ip": ip = arg elif str == "--netmask": netmask = arg elif str == "--gateway": gateway = arg elif str == "--nameserver": nameserver = arg elif str == "--device": device = arg elif str == "--hostname": hostname = arg elif str== "--ethtool": ethtool = arg elif str == "--essid": essid = arg elif str == "--wepkey": wepkey = arg elif str== "--onboot": if arg == 'no': onboot = 0 else: onboot = 1 elif str == "--class": dhcpclass = arg self.setNetwork(id, bootProto, ip, netmask, ethtool, device=device, onboot=onboot, dhcpclass=dhcpclass, essid=essid, wepkey=wepkey) if hostname != "": self.setHostname(id, hostname, override = 1) if nameserver != "": self.setNameserver(id, nameserver) if gateway != "": self.setGateway(id, gateway) def doLang(self, id, args): self.setLanguage(id, args[0]) self.skipSteps.append("language") def doLangSupport (self, id, args): (args, extra) = isys.getopt(args, '', [ 'default=' ]) deflang = "en_US.UTF-8" if args: deflang = args[0][1] else: # if they specified no default we default to en_US if # they installed support for more than one lang, otherwise # we default to the one language they specified support for if extra is None: deflang = "en_US.UTF-8" elif len(extra) >= 1: deflang = extra[0] else: deflang = "en_US.UTF-8" self.setLanguageDefault (id, deflang) self.setLanguageSupport(id, extra) self.skipSteps.append("languagesupport") def doKeyboard(self, id, args): self.setKeyboard(id, args[0]) id.keyboard.beenset = 1 self.skipSteps.append("keyboard") def doZeroMbr(self, id, args): self.setZeroMbr(id, 1) def doMouse(self, id, args): #Don't do anything with mice anymore return ## (args, extra) = isys.getopt(args, '', [ 'device=', 'emulthree' ]) ## mouseType = "none" ## device = None ## emulThree = 0 ## for n in args: ## (str, arg) = n ## if str == "--device": ## device = arg ## elif str == "--emulthree": ## emulThree = 1 ## if extra: ## mouseType = extra[0] ## if mouseType != "none": ## self.setMouse(id, mouseType, device, emulThree) ## self.skipSteps.append("mouse") def doReboot(self, id, args): self.skipSteps.append("complete") def doSkipX(self, id, args): self.skipSteps.append("checkmonitorok") self.skipSteps.append("setsanex") self.skipSteps.append("videocard") self.skipSteps.append("monitor") self.skipSteps.append("xcustom") self.skipSteps.append("handleX11pkgs") self.skipSteps.append("writexconfig") if id.xsetup is not None: id.xsetup.skipx = 1 def doInteractive(self, id, args): self.interactive = 1 def doAutoStep(self, id, args): flags.autostep = 1 flags.autoscreenshot = 0 (xargs, xtra) = isys.getopt(args, '', ['autoscreenshot']) for n in xargs: (str, arg) = n if str == "--autoscreenshot": flags.autoscreenshot = 1 # read the kickstart config... if parsePre is set, only parse # the %pre, otherwise ignore the %pre. assume we're starting in where def readKickstart(self, id, file, parsePre = 0, where = "commands"): handlers = { "auth" : self.doAuthconfig , "authconfig" : self.doAuthconfig , "autopart" : self.doAutoPart , "cdrom" : None , "clearpart" : self.doClearPart , "ignoredisk" : self.doIgnoreDisk , "device" : None , "deviceprobe" : None , "driverdisk" : None , "firewall" : self.doFirewall , "selinux" : self.doSELinux , "harddrive" : None , "install" : None , "keyboard" : self.doKeyboard , "lang" : self.doLang , "langsupport" : self.doLangSupport , "lilo" : self.doLilo , "bootloader" : self.doBootloader , "lilocheck" : self.doLiloCheck , "mouse" : self.doMouse , "network" : self.doNetwork , "nfs" : None , "part" : self.definePartition , "partition" : self.definePartition , "raid" : self.defineRaid , "volgroup" : self.defineVolumeGroup, "logvol" : self.defineLogicalVolume, "reboot" : self.doReboot , "poweroff" : self.doReboot , "halt" : self.doReboot , "shutdown" : self.doReboot , "rootpw" : self.doRootPw , "skipx" : self.doSkipX , "text" : None , "graphical" : None , "cmdline" : None , "timezone" : self.doTimezone , "url" : None , "upgrade" : self.doUpgrade , "xconfig" : self.doXconfig , "monitor" : self.doMonitor , "xdisplay" : None , "zerombr" : self.doZeroMbr , "interactive" : self.doInteractive , "autostep" : self.doAutoStep , "firstboot" : self.doFirstboot , "vnc" : None , } packages = [] groups = [] excludedPackages = [] script = "" scriptInterp = "/bin/sh" scriptLog = None if where == "pre" or where == "traceback": scriptChroot = 0 else: scriptChroot = 1 for n in open(file).readlines(): args = isys.parseArgv(n) # don't eliminate white space or comments from scripts if where not in ["pre", "post", "traceback"]: if not args or args[0][0] == '#': continue if args and (args[0] in ["%pre", "%post", "%traceback"]): if ((where =="pre" and parsePre) or (where in ["post", "traceback"] and not parsePre)): s = Script(script, scriptInterp, scriptChroot, scriptLog) if where == "pre": self.preScripts.append(s) elif where == "post": self.postScripts.append(s) else: self.tracebackScripts.append(s) where = args[0][1:] args = isys.parseArgv(n) script = "" scriptInterp = "/bin/sh" scriptLog = None if where == "pre" or where == "traceback": scriptChroot = 0 else: scriptChroot = 1 argList = [ 'interpreter=', "log=", "logfile=" ] if where == "post": argList.append('nochroot') (args, extra) = isys.getopt(args, '', argList) for n in args: (str, arg) = n if str == "--nochroot": scriptChroot = 0 elif str == "--interpreter": scriptInterp = arg elif str == "--log" or str == "--logfile": scriptLog = arg elif args and args[0] == "%include" and not parsePre: if len(args) < 2: raise KickstartError, "Invalid %include line" else: # read in the included file and set our where appropriately where = self.readKickstart(id, args[1], where = where) elif args and args[0] == "%packages": if ((where =="pre" and parsePre) or (where in ["post", "traceback"] and not parsePre)): s = Script(script, scriptInterp, scriptChroot, scriptLog) if where == "pre": self.preScripts.append(s) elif where == "post": self.postScripts.append(s) else: self.tracebackScripts.append(s) # if we're parsing the %pre, we don't need to continue if parsePre: continue if len(args) > 1: for arg in args[1:]: if arg == "--resolvedeps": id.handleDeps = RESOLVE_DEPS elif arg == "--ignoredeps": id.handleDeps = IGNORE_DEPS elif arg == "--excludedocs": id.excludeDocs = 1 elif arg == "--ignoremissing": self.handleMissing = KS_MISSING_IGNORE elif arg == "--nobase": self.addBase = 0 where = "packages" self.skipSteps.append("package-selection") else: # if we're parsing the %pre and not in the pre, continue if parsePre and where != "pre": continue elif where == "packages": #Scan for comments in package list...drop off #everything after "#" mark try: ind = string.index(n, "#") n = n[:ind] except: #No "#" found in line pass if n[0] == '@': n = n[1:] n = string.strip (n) groups.append(n) elif n[0] == '-': n = n[1:] n = string.strip(n) excludedPackages.append(n) else: n = string.strip (n) packages.append(n) elif where == "commands": if handlers.has_key(args[0]): if handlers[args[0]] is not None: handlers[args[0]](id, args[1:]) else: # unrecognized command raise KickstartError, "Unrecognized ks command: %s\nOn the line: %s" % (args[0], n) elif where in ["pre", "post", "traceback"]: script = script + n else: raise KickstartError, "I'm lost in kickstart" self.groupList.extend(groups) self.packageList.extend(packages) self.excludedList.extend(excludedPackages) # test to see if they specified to clear partitions and also # tried to --onpart on a logical partition # # XXX # #if iutil.getArch() == 'i386' and self.fstab: #clear = self.getClearParts() #if clear == FSEDIT_CLEAR_LINUX or clear == FSEDIT_CLEAR_ALL: #for (mntpoint, (dev, fstype, reformat)) in self.fstab: #if int(dev[-1:]) > 4: #raise RuntimeError, "Clearpart and --onpart on non-primary partition %s not allowed" % dev if ((where =="pre" and parsePre) or (where in ["post", "traceback"] and not parsePre)): s = Script(script, scriptInterp, scriptChroot, scriptLog) if where == "pre": self.preScripts.append(s) elif where == "post": self.postScripts.append(s) else: self.tracebackScripts.append(s) return where def doClearPart(self, id, args): type = CLEARPART_TYPE_NONE drives = None initAll = 0 (args, extra) = isys.getopt(args, '', [ 'linux', 'all', 'drives=', 'initlabel', 'none']) for n in args: (str, arg) = n if str == '--linux': type = CLEARPART_TYPE_LINUX elif str == '--all': type = CLEARPART_TYPE_ALL elif str == '--drives': drives = string.split(arg, ',') elif str == '--initlabel': initAll = 1 elif str == '--none': type = CLEARPART_TYPE_NONE self.setClearParts(id, type, drives, initAll = initAll) # this adds a partition to the autopartition list replacing anything # else with this mountpoint so that you can use autopart and override / def addPartRequest(self, partitions, request): if not request.mountpoint: partitions.autoPartitionRequests.append(request) return for req in partitions.autoPartitionRequests: if req.mountpoint and req.mountpoint == request.mountpoint: partitions.autoPartitionRequests.remove(req) break partitions.autoPartitionRequests.append(request) def doAutoPart(self, id, args): # sets up default autopartitioning. use clearpart separately # if you want it self.setDefaultPartitioning(id, doClear = 0) id.partitions.isKickstart = 1 self.skipSteps.append("partition") self.skipSteps.append("partitionmethod") self.skipSteps.append("partitionmethodsetup") self.skipSteps.append("fdisk") self.skipSteps.append("autopartition") self.skipSteps.append("zfcpconfig") def defineLogicalVolume(self, id, args): (args, extra) = isys.getopt(args, '', [ 'vgname=', 'size=', 'name=', 'fstype=', 'percent=', 'maxsize=', 'grow', 'recommended', 'noformat', 'useexisting']) mountpoint = None vgname = None size = None name = None fstype = None percent = None grow = 0 maxSizeMB = 0 format = 1 recommended = None preexist = 0 for n in args: (str, arg) = n if str == '--vgname': vgname = arg elif str == '--size': size = int(arg) elif str == '--name': name = arg elif str == '--fstype': fstype = arg elif str == '--percent': percent = int(arg) elif str == '--maxsize': maxSizeMB = int(arg) elif str == '--grow': grow = 1 elif str == '--recommended': recommended = 1 elif str == "--noformat": format = 0 preexist = 1 elif str == "--useexisting": preexist = 1 if extra[0] == 'swap': filesystem = fileSystemTypeGet('swap') mountpoint = None if recommended: (size, maxSizeMB) = iutil.swapSuggestion() grow = 1 else: if fstype: filesystem = fileSystemTypeGet(fstype) else: filesystem = fileSystemTypeGetDefault() mountpoint = extra[0] # sanity check mountpoint if mountpoint is not None and mountpoint[0] != '/': raise KickstartError, "The mount point \"%s\" is not valid." % (mountpoint,) if not vgname: raise KickstartError, "Must specify the volume group for the logical volume to be in" if not size and not percent and not preexist: raise KickstartError, "Must specify the size of a logical volume" if percent and percent <= 0 or percent > 100: raise KickstartValueError, "Logical Volume percentage must be between 0 and 100 percent" if not name: raise KickstartError, "Must specify a logical volume name" vgid = self.ksVGMapping[vgname] for areq in id.partitions.autoPartitionRequests: if areq.type == REQUEST_LV: if areq.volumeGroup == vgid and areq.logicalVolumeName == name: raise KickstartValueError, "Logical volume name %s already used in volume group %s" % (name,vgname) if not self.ksVGMapping.has_key(vgname): raise KickstartValueError, "Logical volume specifies a non-existent volume group" request = partRequests.LogicalVolumeRequestSpec(filesystem, format = format, mountpoint = mountpoint, size = size, percent = percent, volgroup = vgid, lvname = name, grow = grow, maxSizeMB=maxSizeMB, preexist = preexist) self.addPartRequest(id.partitions, request) def defineVolumeGroup(self, id, args): (args, extra) = isys.getopt(args, '', ['noformat','useexisting', 'pesize=']) preexist = 0 format = 1 pesize = 32768 vgname = extra[0] for n in args: (str, arg) = n if str == '--noformat' or str == '--useexisting': preexist = 1 format = 0 elif str == "--pesize": pesize = int(arg) pvs = [] # get the unique ids of each of the physical volumes for pv in extra[1:]: if pv not in self.ksPVMapping.keys(): raise KickstartError, "Tried to use an undefined partition in Volume Group specification" pvs.append(self.ksPVMapping[pv]) if len(pvs) == 0 and not preexist: raise KickstartError, "Volume group defined without any physical volumes" if pesize not in lvm.getPossiblePhysicalExtents(floor=1024): raise KickstartError, "Volume group specified invalid pesize: %d" %(pesize,) # get a sort of hackish id uniqueID = self.ksID self.ksVGMapping[extra[0]] = uniqueID self.ksID = self.ksID + 1 request = partRequests.VolumeGroupRequestSpec(vgname = vgname, physvols = pvs, preexist = preexist, format = format, pesize = pesize) request.uniqueID = uniqueID self.addPartRequest(id.partitions, request) def defineRaid(self, id, args): (args, extra) = isys.getopt(args, '', [ 'level=', 'device=', 'spares=', 'fstype=', 'noformat', 'useexisting'] ) level = None raidDev = None spares = 0 fstype = None format = 1 uniqueID = None preexist = 0 for n in args: (str, arg) = n if str == '--level': level = arg elif str == "--device": raidDev = arg if raidDev[0:2] == "md": raidDev = raidDev[2:] raidDev = int(raidDev) elif str == "--spares": spares = int(arg) elif str == "--noformat": format = 0 preexist = 1 elif str == "--useexisting": preexist = 1 elif str == "--fstype": fstype = arg if extra[0] == 'swap': filesystem = fileSystemTypeGet('swap') mountpoint = None elif extra[0].startswith("pv."): filesystem = fileSystemTypeGet("physical volume (LVM)") mountpoint = None if self.ksPVMapping.has_key(extra[0]): raise KickstartError, "Defined PV partition %s multiple times" % (extra[0],) # get a sort of hackish id uniqueID = self.ksID self.ksPVMapping[extra[0]] = uniqueID self.ksID = self.ksID + 1 else: if fstype: filesystem = fileSystemTypeGet(fstype) else: filesystem = fileSystemTypeGetDefault() mountpoint = extra[0] # sanity check mountpoint if mountpoint is not None and mountpoint[0] != '/': raise KickstartError, "The mount point \"%s\" is not valid." % (mountpoint,) raidmems = [] # get the unique ids of each of the raid members for member in extra[1:]: if member not in self.ksRaidMapping.keys(): raise KickstartError, "Tried to use an undefined partition in RAID specification" if member in self.ksUsedMembers: raise KickstartError, "Tried to use the RAID member %s in two or more RAID specifications" % (member,) raidmems.append(self.ksRaidMapping[member]) self.ksUsedMembers.append(member) # XXX this shouldn't have to happen =\ if raid.isRaid0(level): level = "RAID0" elif raid.isRaid1(level): level = "RAID1" elif raid.isRaid5(level): level = "RAID5" elif raid.isRaid6(level): level = "RAID6" if not level and preexist == 0: raise KickstartValueError, "RAID Partition defined without RAID level" if len(raidmems) == 0 and preexist == 0: raise KickstartValueError, "RAID Partition defined without any RAID members" request = partRequests.RaidRequestSpec(filesystem, mountpoint = mountpoint, raidmembers = raidmems, raidlevel = level, raidspares = spares, format = format, raidminor = raidDev, preexist = preexist) if uniqueID: request.uniqueID = uniqueID if preexist and raidDev is not None: request.device = "md%s" %(raidDev,) self.addPartRequest(id.partitions, request) def definePartition(self, id, args): # we set up partition requests (whee!) size = None grow = None maxSize = None disk = None onPart = None fsopts = None type = None primOnly = None format = 1 fstype = None mountpoint = None uniqueID = None start = None end = None badblocks = None recommended = None (args, extra) = isys.getopt(args, '', [ 'size=', 'maxsize=', 'grow', 'onpart=', 'ondisk=', 'bytes-per-inode=', 'usepart=', 'type=', 'fstype=', 'asprimary', 'noformat', 'start=', 'end=', 'badblocks', 'recommended', 'ondrive=', 'onbiosdisk=' ]) for n in args: (str, arg) = n if str == '--size': size = int(arg) elif str == '--maxsize': maxSize = int(arg) elif str == '--grow': grow = 1 elif str == '--onpart' or str == '--usepart': onPart = arg elif str == '--ondisk' or str == '--ondrive': disk = arg elif str == '--onbiosdisk': disk = isys.doGetBiosDisk(arg) if disk is None: raise KickstartValueError, "Specified BIOS disk %s cannot be determined" %(arg,) elif str == '--bytes-per-inode': fsopts = ['-i', arg] # XXX this doesn't do anything right now elif str == '--type': type = int(arg) elif str == "--active": active = 1 elif str == "--asprimary": primOnly = 1 elif str == "--noformat": format = 0 elif str == "--fstype": fstype = arg elif str == "--start": start = int(arg) elif str == "--end": end = int(arg) elif str == "--badblocks": # no longer support badblocks checking log("WARNING: --badblocks specified but is no longer supported") elif str == "--recommended": recommended = 1 if len(extra) != 1: raise KickstartValueError, "partition command requires one anonymous argument" if extra[0] == 'swap': filesystem = fileSystemTypeGet('swap') mountpoint = None if recommended: (size, maxSize) = iutil.swapSuggestion() grow = 1 # if people want to specify no mountpoint for some reason, let them # this is really needed for pSeries boot partitions :( elif extra[0] == 'None': mountpoint = None if fstype: filesystem = fileSystemTypeGet(fstype) else: filesystem = fileSystemTypeGetDefault() elif extra[0] == 'prepboot': filesystem = fileSystemTypeGet("PPC PReP Boot") mountpoint = None elif extra[0].startswith("raid."): filesystem = fileSystemTypeGet("software RAID") if self.ksRaidMapping.has_key(extra[0]): raise KickstartError, "Defined RAID partition %s multiple times" % (extra[0],) # get a sort of hackish id uniqueID = self.ksID self.ksRaidMapping[extra[0]] = uniqueID self.ksID = self.ksID + 1 elif extra[0].startswith("pv."): filesystem = fileSystemTypeGet("physical volume (LVM)") if self.ksPVMapping.has_key(extra[0]): raise KickstartError, "Defined PV partition %s multiple times" % (extra[0],) # get a sort of hackish id uniqueID = self.ksID self.ksPVMapping[extra[0]] = uniqueID self.ksID = self.ksID + 1 # XXX should we let people not do this for some reason? elif extra[0] == "/boot/efi": filesystem = fileSystemTypeGet("vfat") mountpoint = extra[0] else: if fstype: filesystem = fileSystemTypeGet(fstype) mountpoint = extra[0] else: filesystem = fileSystemTypeGetDefault() mountpoint = extra[0] if (size is None) and (not start and not end) and (not onPart): raise KickstartValueError, "partition command requires a size specification" if start and not disk: raise KickstartValueError, "partition command with start cylinder requires a drive specification" if disk and disk not in isys.hardDriveDict().keys(): raise KickstartValueError, "specified disk %s in partition command which does not exist" %(disk,) # XXX bytes per inode is the only per fs option at the moment # and we can assume that it works like this since it only works # with ext[23] if fsopts: filesystem.extraFormatArgs.extend(fsopts) request = partRequests.PartitionSpec(filesystem, mountpoint = mountpoint, format = 1) if size: request.size = size if start: request.start = start if end: request.end = end if grow: request.grow = 1 if maxSize: request.maxSizeMB = maxSize if disk: request.drive = [ disk ] if primOnly: request.primary = 1 if not format: request.format = 0 if uniqueID: request.uniqueID = uniqueID if badblocks: request.badblocks = badblocks if onPart: # strip spurious /dev if onPart.startswith("/dev/"): onPart = onPart[5:] request.device = onPart for areq in id.partitions.autoPartitionRequests: if areq.device is not None and areq.device == onPart: raise KickstartValueError, "Partition %s already used" %(onPart,) self.addPartRequest(id.partitions, request) id.partitions.isKickstart = 1 self.skipSteps.append("partition") self.skipSteps.append("partitionmethod") self.skipSteps.append("partitionmethodsetup") self.skipSteps.append("fdisk") self.skipSteps.append("autopartition") self.skipSteps.append("zfcpconfig") def doIgnoreDisk(self, id, args): # add disks to ignore list drives = [] (args, extra) = isys.getopt(args, '', [ 'drives=' ]) for n in args: (str, arg) = n if str == '--drives': drives = string.split(arg, ',') self.setIgnoredDisks(id, drives) def setSteps(self, dispatch): if self.installType == "upgrade": from upgradeclass import InstallClass theUpgradeclass = InstallClass(0) theUpgradeclass.setSteps(dispatch) # we have no way to specify migrating yet dispatch.skipStep("upgrademigfind") dispatch.skipStep("upgrademigratefs") dispatch.skipStep("upgradecontinue") dispatch.skipStep("findinstall", permanent = 1) dispatch.skipStep("language") dispatch.skipStep("keyboard") # dispatch.skipStep("mouse") dispatch.skipStep("welcome") dispatch.skipStep("betanag") dispatch.skipStep("installtype") else: BaseInstallClass.setSteps(self, dispatch) dispatch.skipStep("findrootparts") if self.interactive or flags.autostep: dispatch.skipStep("installtype") dispatch.skipStep("partitionmethod") dispatch.skipStep("partitionmethodsetup") dispatch.skipStep("fdisk") dispatch.skipStep("autopartition") dispatch.skipStep("bootdisk") # because these steps depend on the monitor being probed # properly, and will stop you if you have an unprobed monitor, # we should skip them for autostep if flags.autostep: dispatch.skipStep("checkmonitorok") dispatch.skipStep("monitor") return dispatch.skipStep("bootdisk") dispatch.skipStep("welcome") dispatch.skipStep("betanag") dispatch.skipStep("confirminstall") dispatch.skipStep("confirmupgrade") dispatch.skipStep("network") dispatch.skipStep("installtype") # skipping firewall by default, disabled by default dispatch.skipStep("firewall") for n in self.skipSteps: dispatch.skipStep(n) for n in self.showSteps: dispatch.skipStep(n, skip = 0) def setInstallData(self, id, intf = None): BaseInstallClass.setInstallData(self, id) self.setEarlySwapOn(1) self.postScripts = [] self.preScripts = [] self.tracebackScripts = [] self.installType = "install" self.id = id self.id.firstboot = FIRSTBOOT_SKIP # parse the %pre try: self.readKickstart(id, self.file, parsePre = 1) except KickstartError, e: raise KickstartError, e log("Running kickstart %%pre script(s)") for script in self.preScripts: script.run("/", self.serial) log("All kickstart %%pre script(s) have been run") # now read the kickstart file for real try: self.readKickstart(id, self.file) except KickstartError, e: log("Exception parsing ks.cfg: %s" %(e,)) if intf is None: raise KickstartError, e else: intf.kickstartErrorWindow(e.__str__()) def runTracebackScripts(self): log("Running kickstart %%traceback script(s)") for script in self.tracebackScripts: script.run("/", self.serial) # Note that this assumes setGroupSelection() is called before # setPackageSelection() def setPackageSelection(self, hdlist, intf): for n in self.packageList: # allow arch:name syntax if n.find("."): fields = n.split(".") name = string.join(fields[:-1], ".") arch = fields[-1] if hdlist.pkgnames.has_key(name): pkgs = hdlist.pkgnames[name] for (nevra, parch) in pkgs: if parch == arch: hdlist.pkgs[nevra].select() continue if hdlist.has_key(n): hdlist[n].select() elif self.handleMissing == KS_MISSING_IGNORE: log("package %s doesn't exist, ignoring" %(n,)) else: rc = intf.messageWindow(_("Missing Package"), _("You have specified that the " "package '%s' should be installed. " "This package does not exist. " "Would you like to continue or " "abort your installation?") %(n,), type="custom", custom_buttons=[_("_Abort"), _("_Continue")]) if rc == 0: sys.exit(1) else: pass def setGroupSelection(self, grpset, intf): grpset.unselectAll() if self.addBase: grpset.selectGroup("base") for n in self.groupList: try: grpset.selectGroup(n) except KeyError: if self.handleMissing == KS_MISSING_IGNORE: log("group %s doesn't exist, ignoring" %(n,)) else: rc = intf.messageWindow(_("Missing Group"), _("You have specified that the " "group '%s' should be installed. " "This group does not exist. " "Would you like to continue or " "abort your installation?") %(n,), type="custom", custom_buttons=[_("_Abort"), _("_Continue")]) if rc == 0: sys.exit(1) else: pass for n in self.excludedList: # allow arch:name syntax if n.find("."): fields = n.split(".") name = string.join(fields[:-1], ".") arch = fields[-1] if grpset.hdrlist.pkgnames.has_key(name): pkgs = grpset.hdrlist.pkgnames[name] for (nevra, parch) in pkgs: if parch == arch: grpset.hdrlist.pkgs[nevra].unselect(isManual = 1) continue if grpset.hdrlist.has_key(n): grpset.hdrlist[n].unselect(isManual = 1) else: log("%s does not exist, can't exclude" %(n,)) def __init__(self, file, serial): self.serial = serial self.file = file self.skipSteps = [] self.showSteps = [] self.interactive = 0 self.addBase = 1 self.packageList = [] self.groupList = [] self.excludedList = [] self.ksRaidMapping = {} self.ksUsedMembers = [] self.ksPVMapping = {} self.ksVGMapping = {} # XXX hack to give us a starting point for RAID, LVM, etc unique IDs. self.ksID = 100000 # how to handle missing packages self.handleMissing = KS_MISSING_PROMPT BaseInstallClass.__init__(self, 0) def Kickstart(file, serial): f = open(file, "r") lines = f.readlines() f.close() passedLines = [] while lines: l = lines[0] lines = lines[1:] if l == "%installclass\n": break passedLines.append(l) if lines: newKsFile = file + ".new" f = open(newKsFile, "w") f.writelines(passedLines) f.close() f = open('/tmp/ksclass.py', "w") f.writelines(lines) f.close() oldPath = sys.path sys.path.append('/tmp') from ksclass import CustomKickstart os.unlink("/tmp/ksclass.py") ksClass = CustomKickstart(newKsFile, serial) os.unlink(newKsFile) else: ksClass = KickstartBase(file, serial) return ksClass # see if any vnc parameters are specified in the kickstart file def parseKickstartVNC(ksfile): try: f = open(ksfile, "r") except: raise KSAppendException("Unable to open ks file %s" % (ksfile,)) lines = f.readlines() f.close() usevnc = 0 vnchost = None vncport = None vncpasswd = None for l in lines: args = isys.parseArgv(l) if args: if args[0] in ("%pre", "%post", "%traceback", "%packages"): break if args[0] != 'vnc': continue else: continue idx = 1 while idx < len(args): if args[idx] == "--password": try: vncpasswd = args[idx+1] except: raise KickstartError, "Missing argument to vnc --password option" idx += 2 elif args[idx] == "--connect": try: connectspec = args[idx+1] except: raise KickstartError, "Missing argument to vnc --connect option" cargs = string.split(connectspec, ":") vnchost = cargs[0] if len(cargs) > 1: if len(cargs[1]) > 0: vncport = cargs[1] idx += 2 else: raise KickstartError, "Unknown vnc option %s" % (args[idx],) usevnc = 1 break return (usevnc, vncpasswd, vnchost, vncport) # # look through ksfile and if it contains a line: # # %ksappend # # pull down and append to /tmp/ks.cfg. This is run before we actually # parse the complete kickstart file. # # Main use is to have the ks.cfg you send to the loader by minimal, and then # use %ksappend to pull via https anything private (like passwords, etc) in # the second stage. # def pullRemainingKickstartConfig(ksfile): try: f = open(ksfile, "r") except: raise KSAppendException("Unable to open ks file %s" % (ksfile,)) lines = f.readlines() f.close() url = None for l in lines: ll = l.strip() if string.find(ll, "%ksappend") == -1: continue try: (xxx, ksurl) = string.split(ll, ' ') except: raise KSAppendException("Illegal url for %%ksappend - %s" % (ll,)) log("Attempting to pull second part of ks.cfg from url %s" % (ksurl,)) try: url = urllib2.urlopen(ksurl) except urllib2.HTTPError, e: raise KSAppendException("IOError: %s:%s" % (e.code, e.msg)) except urllib2.URLError, e: raise KSAppendException("IOError: -1:%s" % (e.reason,)) else: # sanity check result - sometimes FTP doesnt # catch a file is missing try: clen = url.info()['content-length'] except Exception, e: clen = 0 if clen < 1: raise KSAppendException("IOError: -1:File not found") break # if we got something then rewrite /tmp/ks.cfg with new information if url is not None: os.rename("/tmp/ks.cfg", "/tmp/ks.cfg-part1") # insert contents of original /tmp/ks.cfg w/o %ksappend line f = open("/tmp/ks.cfg", 'w+') for l in lines: ll = l.strip() if string.find(ll, "%ksappend") != -1: continue f.write(l) # now write part we just grabbed f.write(url.read()) f.close() # close up url and we're done url.close() return None