2 # kickstart.py : Apply kickstart configuration to a system
4 # Copyright 2007, Red Hat Inc.
5 # Copyright 2009, 2010, 2011 Intel, Inc.
7 # This program is free software; you can redistribute it and/or modify
8 # it under the terms of the GNU General Public License as published by
9 # the Free Software Foundation; version 2 of the License.
11 # This program is distributed in the hope that it will be useful,
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 # GNU Library General Public License for more details.
16 # You should have received a copy of the GNU General Public License
17 # along with this program; if not, write to the Free Software
18 # Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
27 from mic.utils import errors, misc, runner, fs_related as fs
29 import pykickstart.commands as kscommands
30 import pykickstart.constants as ksconstants
31 import pykickstart.errors as kserrors
32 import pykickstart.parser as ksparser
33 import pykickstart.version as ksversion
34 from pykickstart.handlers.control import commandMap
35 from pykickstart.handlers.control import dataMap
37 import custom_commands.desktop as desktop
38 import custom_commands.moblinrepo as moblinrepo
39 import custom_commands.micboot as micboot
41 def read_kickstart(path):
42 """Parse a kickstart file and return a KickstartParser instance.
44 This is a simple utility function which takes a path to a kickstart file,
45 parses it and returns a pykickstart KickstartParser instance which can
46 be then passed to an ImageCreator constructor.
48 If an error occurs, a CreatorError exception is thrown.
51 #version = ksversion.makeVersion()
52 #ks = ksparser.KickstartParser(version)
54 using_version = ksversion.DEVEL
55 commandMap[using_version]["desktop"] = desktop.Moblin_Desktop
56 commandMap[using_version]["repo"] = moblinrepo.Moblin_Repo
57 commandMap[using_version]["bootloader"] = micboot.Moblin_Bootloader
58 dataMap[using_version]["RepoData"] = moblinrepo.Moblin_RepoData
59 superclass = ksversion.returnClassForVersion(version=using_version)
61 class KSHandlers(superclass):
62 def __init__(self, mapping={}):
63 superclass.__init__(self, mapping=commandMap[using_version])
65 ks = ksparser.KickstartParser(KSHandlers())
68 ks.readKickstart(path)
69 except kserrors.KickstartError, e:
70 raise errors.KsError("'%s': %s" % (path, str(e)))
71 except kserrors.KickstartParseError, e:
72 raise errors.KsError("'%s': %s" % (path, str(e)))
75 def build_name(kscfg, prefix = None, suffix = None, maxlen = None):
76 """Construct and return an image name string.
78 This is a utility function to help create sensible name and fslabel
79 strings. The name is constructed using the sans-prefix-and-extension
80 kickstart filename and the supplied prefix and suffix.
82 If the name exceeds the maxlen length supplied, the prefix is first dropped
83 and then the kickstart filename portion is reduced until it fits. In other
84 words, the suffix takes precedence over the kickstart portion and the
85 kickstart portion takes precedence over the prefix.
87 kscfg -- a path to a kickstart file
88 prefix -- a prefix to prepend to the name; defaults to None, which causes
90 suffix -- a suffix to append to the name; defaults to None, which causes
91 a YYYYMMDDHHMM suffix to be used
92 maxlen -- the maximum length for the returned string; defaults to None,
93 which means there is no restriction on the name length
95 Note, if maxlen is less then the len(suffix), you get to keep both pieces.
98 name = os.path.basename(kscfg)
106 suffix = time.strftime("%Y%m%d%H%M")
108 if name.startswith(prefix):
109 name = name[len(prefix):]
111 ret = prefix + name + "-" + suffix
112 if not maxlen is None and len(ret) > maxlen:
113 ret = name[:maxlen - len(suffix) - 1] + "-" + suffix
117 class KickstartConfig(object):
118 """A base class for applying kickstart configurations to a system."""
119 def __init__(self, instroot):
120 self.instroot = instroot
122 def path(self, subpath):
123 return self.instroot + subpath
126 os.chroot(self.instroot)
129 def call(self, args):
130 if not os.path.exists("%s/%s" %(self.instroot, args[0])):
131 msger.warning("%s/%s" %(self.instroot, args[0]))
132 raise errors.KsError("Unable to run %s!" %(args))
133 subprocess.call(args, preexec_fn = self.chroot)
138 class LanguageConfig(KickstartConfig):
139 """A class to apply a kickstart language configuration to a system."""
140 def apply(self, kslang):
142 f = open(self.path("/etc/sysconfig/i18n"), "w+")
143 f.write("LANG=\"" + kslang.lang + "\"\n")
146 class KeyboardConfig(KickstartConfig):
147 """A class to apply a kickstart keyboard configuration to a system."""
148 def apply(self, kskeyboard):
151 # should this impact the X keyboard config too?
152 # or do we want to make X be able to do this mapping?
154 #k = rhpl.keyboard.Keyboard()
155 #if kskeyboard.keyboard:
156 # k.set(kskeyboard.keyboard)
157 #k.write(self.instroot)
160 class TimezoneConfig(KickstartConfig):
161 """A class to apply a kickstart timezone configuration to a system."""
162 def apply(self, kstimezone):
163 tz = kstimezone.timezone or "America/New_York"
164 utc = str(kstimezone.isUtc)
166 f = open(self.path("/etc/sysconfig/clock"), "w+")
167 f.write("ZONE=\"" + tz + "\"\n")
168 f.write("UTC=" + utc + "\n")
171 shutil.copyfile(self.path("/usr/share/zoneinfo/%s" %(tz,)),
172 self.path("/etc/localtime"))
173 except (IOError, OSError), (errno, msg):
174 raise errors.KsError("Error copying timezone info: %s" %(msg,))
177 class AuthConfig(KickstartConfig):
178 """A class to apply a kickstart authconfig configuration to a system."""
179 def apply(self, ksauthconfig):
180 auth = ksauthconfig.authconfig or "--useshadow --enablemd5"
181 args = ["/usr/share/authconfig/authconfig.py", "--update", "--nostart"]
182 self.call(args + auth.split())
184 class FirewallConfig(KickstartConfig):
185 """A class to apply a kickstart firewall configuration to a system."""
186 def apply(self, ksfirewall):
188 # FIXME: should handle the rest of the options
190 if not os.path.exists(self.path("/usr/sbin/lokkit")):
192 if ksfirewall.enabled:
195 status = "--disabled"
197 self.call(["/usr/sbin/lokkit",
198 "-f", "--quiet", "--nostart", status])
200 class RootPasswordConfig(KickstartConfig):
201 """A class to apply a kickstart root password configuration to a system."""
203 self.call(["/usr/bin/passwd", "-d", "root"])
205 def set_encrypted(self, password):
206 self.call(["/usr/sbin/usermod", "-p", password, "root"])
208 def set_unencrypted(self, password):
209 for p in ("/bin/echo", "/usr/sbin/chpasswd"):
210 if not os.path.exists("%s/%s" %(self.instroot, p)):
211 raise errors.KsError("Unable to set unencrypted password due to lack of %s" % p)
213 p1 = subprocess.Popen(["/bin/echo", "root:%s" %password],
214 stdout = subprocess.PIPE,
215 preexec_fn = self.chroot)
216 p2 = subprocess.Popen(["/usr/sbin/chpasswd", "-m"],
218 stdout = subprocess.PIPE,
219 preexec_fn = self.chroot)
222 def apply(self, ksrootpw):
223 if ksrootpw.isCrypted:
224 self.set_encrypted(ksrootpw.password)
225 elif ksrootpw.password != "":
226 self.set_unencrypted(ksrootpw.password)
230 class UserConfig(KickstartConfig):
231 def set_empty_passwd(self, user):
232 self.call(["/usr/bin/passwd", "-d", user])
234 def set_encrypted_passwd(self, user, password):
235 self.call(["/usr/sbin/usermod", "-p", "%s" % password, user])
237 def set_unencrypted_passwd(self, user, password):
238 for p in ("/bin/echo", "/usr/sbin/chpasswd"):
239 if not os.path.exists("%s/%s" %(self.instroot, p)):
240 raise errors.KsError("Unable to set unencrypted password due to lack of %s" % p)
242 p1 = subprocess.Popen(["/bin/echo", "%s:%s" %(user, password)],
243 stdout = subprocess.PIPE,
244 preexec_fn = self.chroot)
245 p2 = subprocess.Popen(["/usr/sbin/chpasswd", "-m"],
247 stdout = subprocess.PIPE,
248 preexec_fn = self.chroot)
251 def addUser(self, userconfig):
252 args = [ "/usr/sbin/useradd" ]
253 if userconfig.groups:
254 args += [ "--groups", string.join(userconfig.groups, ",") ]
256 args.append(userconfig.name)
257 dev_null = os.open("/dev/null", os.O_WRONLY)
258 subprocess.call(args,
261 preexec_fn = self.chroot)
263 if userconfig.password not in (None, ""):
264 if userconfig.isCrypted:
265 self.set_encrypted_passwd(userconfig.name, userconfig.password)
267 self.set_unencrypted_passwd(userconfig.name, userconfig.password)
269 self.set_empty_passwd(userconfig.name)
271 raise errors.KsError("Invalid kickstart command: %s" % userconfig.__str__())
273 def apply(self, user):
274 for userconfig in user.userList:
276 self.addUser(userconfig)
280 class ServicesConfig(KickstartConfig):
281 """A class to apply a kickstart services configuration to a system."""
282 def apply(self, ksservices):
283 if not os.path.exists(self.path("/sbin/chkconfig")):
285 for s in ksservices.enabled:
286 self.call(["/sbin/chkconfig", s, "on"])
287 for s in ksservices.disabled:
288 self.call(["/sbin/chkconfig", s, "off"])
290 class XConfig(KickstartConfig):
291 """A class to apply a kickstart X configuration to a system."""
292 def apply(self, ksxconfig):
294 f = open(self.path("/etc/inittab"), "rw+")
296 buf = buf.replace("id:3:initdefault", "id:5:initdefault")
300 if ksxconfig.defaultdesktop:
301 f = open(self.path("/etc/sysconfig/desktop"), "w")
302 f.write("DESKTOP="+ksxconfig.defaultdesktop+"\n")
305 class DesktopConfig(KickstartConfig):
306 """A class to apply a kickstart desktop configuration to a system."""
307 def apply(self, ksdesktop):
308 if ksdesktop.defaultdesktop:
309 f = open(self.path("/etc/sysconfig/desktop"), "w")
310 f.write("DESKTOP="+ksdesktop.defaultdesktop+"\n")
312 if os.path.exists(self.path("/etc/gdm/custom.conf")):
313 f = open(self.path("/etc/skel/.dmrc"), "w")
314 f.write("[Desktop]\n")
315 f.write("Session="+ksdesktop.defaultdesktop.lower()+"\n")
317 if ksdesktop.session:
318 if os.path.exists(self.path("/etc/sysconfig/uxlaunch")):
319 f = open(self.path("/etc/sysconfig/uxlaunch"), "a+")
320 f.write("session="+ksdesktop.session.lower()+"\n")
322 if ksdesktop.autologinuser:
323 f = open(self.path("/etc/sysconfig/desktop"), "a+")
324 f.write("AUTOLOGIN_USER=" + ksdesktop.autologinuser + "\n")
326 if ksdesktop.session:
327 if os.path.exists(self.path("/etc/sysconfig/uxlaunch")):
328 f = open(self.path("/etc/sysconfig/uxlaunch"), "a+")
329 f.write("user="+ksdesktop.autologinuser+"\n")
331 if os.path.exists(self.path("/etc/gdm/custom.conf")):
332 f = open(self.path("/etc/gdm/custom.conf"), "w")
333 f.write("[daemon]\n")
334 f.write("AutomaticLoginEnable=true\n")
335 f.write("AutomaticLogin=" + ksdesktop.autologinuser + "\n")
338 class MoblinRepoConfig(KickstartConfig):
339 """A class to apply a kickstart desktop configuration to a system."""
340 def __create_repo_section(self, repo, type, fd):
343 reposuffix = {"base":"", "debuginfo":"-debuginfo", "source":"-source"}
344 reponame = repo.name + reposuffix[type]
347 baseurl = repo.baseurl
349 mirrorlist = repo.mirrorlist
350 elif type == "debuginfo":
352 if repo.baseurl.endswith("/"):
353 baseurl = os.path.dirname(os.path.dirname(repo.baseurl))
355 baseurl = os.path.dirname(repo.baseurl)
358 variant = repo.mirrorlist[repo.mirrorlist.find("$"):]
359 mirrorlist = repo.mirrorlist[0:repo.mirrorlist.find("$")]
360 mirrorlist += "debug" + "-" + variant
361 elif type == "source":
363 if repo.baseurl.endswith("/"):
364 baseurl = os.path.dirname(os.path.dirname(os.path.dirname(repo.baseurl)))
366 baseurl = os.path.dirname(os.path.dirname(repo.baseurl))
369 variant = repo.mirrorlist[repo.mirrorlist.find("$"):]
370 mirrorlist = repo.mirrorlist[0:repo.mirrorlist.find("$")]
371 mirrorlist += "source" + "-" + variant
373 fd.write("[" + reponame + "]\n")
374 fd.write("name=" + reponame + "\n")
375 fd.write("failovermethod=priority\n")
377 fd.write("baseurl=" + baseurl + "\n")
379 fd.write("mirrorlist=" + mirrorlist + "\n")
380 """ Skip saving proxy settings """
382 # fd.write("proxy=" + repo.proxy + "\n")
383 #if repo.proxy_username:
384 # fd.write("proxy_username=" + repo.proxy_username + "\n")
385 #if repo.proxy_password:
386 # fd.write("proxy_password=" + repo.proxy_password + "\n")
388 fd.write("gpgkey=" + repo.gpgkey + "\n")
389 fd.write("gpgcheck=1\n")
391 fd.write("gpgcheck=0\n")
392 if type == "source" or type == "debuginfo" or repo.disable:
393 fd.write("enabled=0\n")
395 fd.write("enabled=1\n")
398 def __create_repo_file(self, repo, repodir):
399 fs.makedirs(self.path(repodir))
400 f = open(self.path(repodir + "/" + repo.name + ".repo"), "w")
401 self.__create_repo_section(repo, "base", f)
403 self.__create_repo_section(repo, "debuginfo", f)
405 self.__create_repo_section(repo, "source", f)
408 def apply(self, ksrepo, repodata):
409 for repo in ksrepo.repoList:
411 #self.__create_repo_file(repo, "/etc/yum.repos.d")
412 self.__create_repo_file(repo, "/etc/zypp/repos.d")
413 """ Import repo gpg keys """
415 for repo in repodata:
417 runner.quiet(['rpm', "--root=%s" % self.instroot, "--import", repo['repokey']])
419 class RPMMacroConfig(KickstartConfig):
420 """A class to apply the specified rpm macros to the filesystem"""
424 if not os.path.exists(self.path("/etc/rpm")):
425 os.mkdir(self.path("/etc/rpm"))
426 f = open(self.path("/etc/rpm/macros.imgcreate"), "w+")
428 f.write("%_excludedocs 1\n")
429 f.write("%__file_context_path %{nil}\n")
430 if inst_langs(ks) != None:
431 f.write("%_install_langs ")
432 f.write(inst_langs(ks))
436 class NetworkConfig(KickstartConfig):
437 """A class to apply a kickstart network configuration to a system."""
438 def write_ifcfg(self, network):
439 p = self.path("/etc/sysconfig/network-scripts/ifcfg-" + network.device)
444 f.write("DEVICE=%s\n" % network.device)
445 f.write("BOOTPROTO=%s\n" % network.bootProto)
447 if network.bootProto.lower() == "static":
449 f.write("IPADDR=%s\n" % network.ip)
451 f.write("NETMASK=%s\n" % network.netmask)
454 f.write("ONBOOT=on\n")
456 f.write("ONBOOT=off\n")
459 f.write("ESSID=%s\n" % network.essid)
462 if network.ethtool.find("autoneg") == -1:
463 network.ethtool = "autoneg off " + network.ethtool
464 f.write("ETHTOOL_OPTS=%s\n" % network.ethtool)
466 if network.bootProto.lower() == "dhcp":
468 f.write("DHCP_HOSTNAME=%s\n" % network.hostname)
469 if network.dhcpclass:
470 f.write("DHCP_CLASSID=%s\n" % network.dhcpclass)
473 f.write("MTU=%s\n" % network.mtu)
477 def write_wepkey(self, network):
478 if not network.wepkey:
481 p = self.path("/etc/sysconfig/network-scripts/keys-" + network.device)
484 f.write("KEY=%s\n" % network.wepkey)
487 def write_sysconfig(self, useipv6, hostname, gateway):
488 path = self.path("/etc/sysconfig/network")
492 f.write("NETWORKING=yes\n")
495 f.write("NETWORKING_IPV6=yes\n")
497 f.write("NETWORKING_IPV6=no\n")
500 f.write("HOSTNAME=%s\n" % hostname)
502 f.write("HOSTNAME=localhost.localdomain\n")
505 f.write("GATEWAY=%s\n" % gateway)
509 def write_hosts(self, hostname):
511 if hostname and hostname != "localhost.localdomain":
512 localline += hostname + " "
513 l = hostname.split(".")
515 localline += l[0] + " "
516 localline += "localhost.localdomain localhost"
518 path = self.path("/etc/hosts")
521 f.write("127.0.0.1\t\t%s\n" % localline)
522 f.write("::1\t\tlocalhost6.localdomain6 localhost6\n")
525 def write_resolv(self, nodns, nameservers):
526 if nodns or not nameservers:
529 path = self.path("/etc/resolv.conf")
533 for ns in (nameservers):
535 f.write("nameserver %s\n" % ns)
539 def apply(self, ksnet):
540 fs.makedirs(self.path("/etc/sysconfig/network-scripts"))
548 for network in ksnet.network:
549 if not network.device:
550 raise errors.KsError("No --device specified with "
551 "network kickstart command")
553 if (network.onboot and network.bootProto.lower() != "dhcp" and
554 not (network.ip and network.netmask)):
555 raise errors.KsError("No IP address and/or netmask "
556 "specified with static "
557 "configuration for '%s'" %
560 self.write_ifcfg(network)
561 self.write_wepkey(network)
569 hostname = network.hostname
571 gateway = network.gateway
573 if network.nameserver:
574 nameservers = network.nameserver.split(",")
576 self.write_sysconfig(useipv6, hostname, gateway)
577 self.write_hosts(hostname)
578 self.write_resolv(nodns, nameservers)
581 def get_image_size(ks, default = None):
583 for p in ks.handler.partition.partitions:
584 if p.mountpoint == "/" and p.size:
587 return int(__size) * 1024L * 1024L
591 def get_image_fstype(ks, default = None):
592 for p in ks.handler.partition.partitions:
593 if p.mountpoint == "/" and p.fstype:
597 def get_image_fsopts(ks, default = None):
598 for p in ks.handler.partition.partitions:
599 if p.mountpoint == "/" and p.fsopts:
605 if isinstance(ks.handler.device, kscommands.device.FC3_Device):
606 devices.append(ks.handler.device)
608 devices.extend(ks.handler.device.deviceList)
611 for device in devices:
612 if not device.moduleName:
614 modules.extend(device.moduleName.split(":"))
618 def get_timeout(ks, default = None):
619 if not hasattr(ks.handler.bootloader, "timeout"):
621 if ks.handler.bootloader.timeout is None:
623 return int(ks.handler.bootloader.timeout)
625 def get_kernel_args(ks, default = "ro liveimg"):
626 if not hasattr(ks.handler.bootloader, "appendLine"):
628 if ks.handler.bootloader.appendLine is None:
630 return "%s %s" %(default, ks.handler.bootloader.appendLine)
632 def get_menu_args(ks, default = "liveinst"):
633 if not hasattr(ks.handler.bootloader, "menus"):
635 if ks.handler.bootloader.menus in (None, ""):
637 return "%s" % ks.handler.bootloader.menus
639 def get_default_kernel(ks, default = None):
640 if not hasattr(ks.handler.bootloader, "default"):
642 if not ks.handler.bootloader.default:
644 return ks.handler.bootloader.default
646 def get_repos(ks, repo_urls = {}):
648 for repo in ks.handler.repo.repoList:
650 if hasattr(repo, "includepkgs"):
651 inc.extend(repo.includepkgs)
654 if hasattr(repo, "excludepkgs"):
655 exc.extend(repo.excludepkgs)
657 baseurl = repo.baseurl
658 mirrorlist = repo.mirrorlist
660 if repo.name in repo_urls:
661 baseurl = repo_urls[repo.name]
664 if repos.has_key(repo.name):
665 msger.warning("Overriding already specified repo %s" %(repo.name,))
668 if hasattr(repo, "proxy"):
670 proxy_username = None
671 if hasattr(repo, "proxy_username"):
672 proxy_username = repo.proxy_username
673 proxy_password = None
674 if hasattr(repo, "proxy_password"):
675 proxy_password = repo.proxy_password
676 if hasattr(repo, "debuginfo"):
677 debuginfo = repo.debuginfo
678 if hasattr(repo, "source"):
680 if hasattr(repo, "gpgkey"):
682 if hasattr(repo, "disable"):
683 disable = repo.disable
685 repos[repo.name] = (repo.name, baseurl, mirrorlist, inc, exc, proxy, proxy_username, proxy_password, debuginfo, source, gpgkey, disable)
687 return repos.values()
689 def convert_method_to_repo(ks):
691 ks.handler.repo.methodToRepo()
692 except (AttributeError, kserrors.KickstartError):
695 def get_packages(ks, required = []):
696 return ks.handler.packages.packageList + required
698 def get_groups(ks, required = []):
699 return ks.handler.packages.groupList + required
701 def get_excluded(ks, required = []):
702 return ks.handler.packages.excludedList + required
704 def get_partitions(ks, required = []):
705 return ks.handler.partition.partitions
707 def ignore_missing(ks):
708 return ks.handler.packages.handleMissing == ksconstants.KS_MISSING_IGNORE
710 def exclude_docs(ks):
711 return ks.handler.packages.excludeDocs
714 if hasattr(ks.handler.packages, "instLange"):
715 return ks.handler.packages.instLange
716 elif hasattr(ks.handler.packages, "instLangs"):
717 return ks.handler.packages.instLangs
720 def get_post_scripts(ks):
722 for s in ks.handler.scripts:
723 if s.type != ksparser.KS_SCRIPT_POST:
728 def add_repo(ks, repostr):
729 args = repostr.split()
730 repoobj = ks.handler.repo.parse(args[1:])
731 if repoobj and repoobj not in ks.handler.repo.repoList:
732 ks.handler.repo.repoList.append(repoobj)
734 def remove_all_repos(ks):
735 while len(ks.handler.repo.repoList) != 0:
736 del ks.handler.repo.repoList[0]
738 def remove_duplicate_repos(ks):
742 if len(ks.handler.repo.repoList) < 2:
744 if i >= len(ks.handler.repo.repoList) - 1:
746 name = ks.handler.repo.repoList[i].name
747 baseurl = ks.handler.repo.repoList[i].baseurl
748 if j < len(ks.handler.repo.repoList):
749 if (ks.handler.repo.repoList[j].name == name or \
750 ks.handler.repo.repoList[j].baseurl == baseurl):
751 del ks.handler.repo.repoList[j]
754 if j >= len(ks.handler.repo.repoList):
761 def resolve_groups(creator, repometadata, use_comps = False):
762 pkgmgr = creator.pkgmgr.get_default_pkg_manager
764 if creator.pkgmgr.managers.has_key("zypp") and creator.pkgmgr.managers['zypp'] == pkgmgr:
768 for repo in repometadata:
769 """ Mustn't replace group with package list if repo is ready for the corresponding package manager """
770 if iszypp and repo["patterns"] and not use_comps:
772 if not iszypp and repo["comps"] and use_comps:
776 But we also must handle such cases, use zypp but repo only has comps,
777 use yum but repo only has patterns, use zypp but use_comps is true,
778 use yum but use_comps is false.
782 if (use_comps and repo["comps"]) or (not repo["patterns"] and repo["comps"]):
783 groupfile = repo["comps"]
784 get_pkglist_handler = misc.get_pkglist_in_comps
786 if (not use_comps and repo["patterns"]) or (not repo["comps"] and repo["patterns"]):
787 groupfile = repo["patterns"]
788 get_pkglist_handler = misc.get_pkglist_in_patterns
793 if i >= len(ks.handler.packages.groupList):
795 pkglist = get_pkglist_handler(ks.handler.packages.groupList[i].name, groupfile)
797 del ks.handler.packages.groupList[i]
799 if pkg not in ks.handler.packages.packageList:
800 ks.handler.packages.packageList.append(pkg)