if mounting squashfs add ro mount option
[livecd.git] / imgcreate / kickstart.py
blobaca4043557ba29791a9dc12a9462e6c1e3b0f10d
2 # kickstart.py : Apply kickstart configuration to a system
4 # Copyright 2007, Red Hat Inc.
6 # This program is free software; you can redistribute it and/or modify
7 # it under the terms of the GNU General Public License as published by
8 # the Free Software Foundation; version 2 of the License.
10 # This program is distributed in the hope that it will be useful,
11 # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 # GNU Library General Public License for more details.
15 # You should have received a copy of the GNU General Public License
16 # along with this program; if not, write to the Free Software
17 # Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
19 import os
20 import os.path
21 import shutil
22 import subprocess
23 import time
24 import logging
25 import urlgrabber
26 import selinux
28 try:
29 import system_config_keyboard.keyboard as keyboard
30 except ImportError:
31 import rhpl.keyboard as keyboard
33 import pykickstart.commands as kscommands
34 import pykickstart.constants as ksconstants
35 import pykickstart.errors as kserrors
36 import pykickstart.parser as ksparser
37 import pykickstart.version as ksversion
39 import imgcreate.errors as errors
40 import imgcreate.fs as fs
42 def read_kickstart(path):
43 """Parse a kickstart file and return a KickstartParser instance.
45 This is a simple utility function which takes a path to a kickstart file,
46 parses it and returns a pykickstart KickstartParser instance which can
47 be then passed to an ImageCreator constructor.
49 If an error occurs, a CreatorError exception is thrown.
51 """
52 version = ksversion.makeVersion()
53 ks = ksparser.KickstartParser(version)
54 try:
55 ksfile = urlgrabber.urlgrab(path)
56 ks.readKickstart(ksfile)
57 # Fallback to e.args[0] is a workaround for bugs in urlgragger and pykickstart.
58 except IOError, e:
59 raise errors.KickstartError("Failed to read kickstart file "
60 "'%s' : %s" % (path, e.strerror or
61 e.args[0]))
62 except kserrors.KickstartError, e:
63 raise errors.KickstartError("Failed to parse kickstart file "
64 "'%s' : %s" % (path, e))
65 return ks
67 def build_name(kscfg, prefix = None, suffix = None, maxlen = None):
68 """Construct and return an image name string.
70 This is a utility function to help create sensible name and fslabel
71 strings. The name is constructed using the sans-prefix-and-extension
72 kickstart filename and the supplied prefix and suffix.
74 If the name exceeds the maxlen length supplied, the prefix is first dropped
75 and then the kickstart filename portion is reduced until it fits. In other
76 words, the suffix takes precedence over the kickstart portion and the
77 kickstart portion takes precedence over the prefix.
79 kscfg -- a path to a kickstart file
80 prefix -- a prefix to prepend to the name; defaults to None, which causes
81 no prefix to be used
82 suffix -- a suffix to append to the name; defaults to None, which causes
83 a YYYYMMDDHHMM suffix to be used
84 maxlen -- the maximum length for the returned string; defaults to None,
85 which means there is no restriction on the name length
87 Note, if maxlen is less then the len(suffix), you get to keep both pieces.
89 """
90 name = os.path.basename(kscfg)
91 idx = name.rfind('.')
92 if idx >= 0:
93 name = name[:idx]
95 if prefix is None:
96 prefix = ""
97 if suffix is None:
98 suffix = time.strftime("%Y%m%d%H%M")
100 if name.startswith(prefix):
101 name = name[len(prefix):]
103 ret = prefix + name + "-" + suffix
104 if not maxlen is None and len(ret) > maxlen:
105 ret = name[:maxlen - len(suffix) - 1] + "-" + suffix
107 return ret
109 class KickstartConfig(object):
110 """A base class for applying kickstart configurations to a system."""
111 def __init__(self, instroot):
112 self.instroot = instroot
114 def path(self, subpath):
115 return self.instroot + subpath
117 def chroot(self):
118 os.chroot(self.instroot)
119 os.chdir("/")
121 def call(self, args):
122 if not os.path.exists("%s/%s" %(self.instroot, args[0])):
123 raise errors.KickstartError("Unable to run %s!" %(args))
124 subprocess.call(args, preexec_fn = self.chroot)
126 def apply(self):
127 pass
129 class LanguageConfig(KickstartConfig):
130 """A class to apply a kickstart language configuration to a system."""
131 def apply(self, kslang):
132 lang = kslang.lang or "en_US.UTF-8"
134 f = open(self.path("/etc/sysconfig/i18n"), "w+")
135 f.write("LANG=\"" + lang + "\"\n")
136 f.close()
138 class KeyboardConfig(KickstartConfig):
139 """A class to apply a kickstart keyboard configuration to a system."""
140 def apply(self, kskeyboard):
141 k = keyboard.Keyboard()
142 if kskeyboard.keyboard:
143 k.set(kskeyboard.keyboard)
144 k.write(self.instroot)
146 class TimezoneConfig(KickstartConfig):
147 """A class to apply a kickstart timezone configuration to a system."""
148 def apply(self, kstimezone):
149 tz = kstimezone.timezone or "America/New_York"
150 utc = str(kstimezone.isUtc)
152 f = open(self.path("/etc/sysconfig/clock"), "w+")
153 f.write("ZONE=\"" + tz + "\"\n")
154 f.write("UTC=" + utc + "\n")
155 f.close()
156 try:
157 shutil.copy2(self.path("/usr/share/zoneinfo/%s" %(tz,)),
158 self.path("/etc/localtime"))
159 except OSError, e:
160 log.error("Error copying timezone: %s" %(e.strerror,))
163 class AuthConfig(KickstartConfig):
164 """A class to apply a kickstart authconfig configuration to a system."""
165 def apply(self, ksauthconfig):
166 if not os.path.exists(self.path("/usr/sbin/authconfig")):
167 return
169 auth = ksauthconfig.authconfig or "--useshadow --enablemd5"
170 args = ["/usr/sbin/authconfig", "--update", "--nostart"]
171 self.call(args + auth.split())
173 class FirewallConfig(KickstartConfig):
174 """A class to apply a kickstart firewall configuration to a system."""
175 def apply(self, ksfirewall):
176 if not os.path.exists(self.path("/usr/sbin/lokkit")):
177 return
178 args = ["/usr/sbin/lokkit", "-f", "--quiet", "--nostart"]
179 if ksfirewall.enabled:
180 args.append("--enabled")
182 for port in ksfirewall.ports:
183 args.append("--port=%s" %(port,))
184 for svc in ksfirewall.services:
185 args.append("--service=%s" %(svc,))
186 for dev in ksfirewall.trusts:
187 args.append("--trust=%s" %(dev,))
188 else:
189 args.append("--disabled")
191 self.call(args)
193 class RootPasswordConfig(KickstartConfig):
194 """A class to apply a kickstart root password configuration to a system."""
195 def unset(self):
196 self.call(["/usr/bin/passwd", "-d", "root"])
198 def set_encrypted(self, password):
199 self.call(["/usr/sbin/usermod", "-p", password, "root"])
201 def set_unencrypted(self, password):
202 for p in ("/bin/echo", "/usr/bin/passwd"):
203 if not os.path.exists("%s/%s" %(self.instroot, p)):
204 raise errors.KickstartError("Unable to set unencrypted password due to lack of %s" % p)
206 p1 = subprocess.Popen(["/bin/echo", password],
207 stdout = subprocess.PIPE,
208 preexec_fn = self.chroot)
209 p2 = subprocess.Popen(["/usr/bin/passwd", "--stdin", "root"],
210 stdin = p1.stdout,
211 stdout = subprocess.PIPE,
212 preexec_fn = self.chroot)
213 p2.communicate()
215 def apply(self, ksrootpw):
216 if ksrootpw.isCrypted:
217 self.set_encrypted(ksrootpw.password)
218 elif ksrootpw.password != "":
219 self.set_unencrypted(ksrootpw.password)
220 else:
221 self.unset()
223 class ServicesConfig(KickstartConfig):
224 """A class to apply a kickstart services configuration to a system."""
225 def apply(self, ksservices):
226 if not os.path.exists(self.path("/sbin/chkconfig")):
227 return
228 for s in ksservices.enabled:
229 self.call(["/sbin/chkconfig", s, "on"])
230 for s in ksservices.disabled:
231 self.call(["/sbin/chkconfig", s, "off"])
233 class XConfig(KickstartConfig):
234 """A class to apply a kickstart X configuration to a system."""
235 def apply(self, ksxconfig):
236 if ksxconfig.startX:
237 f = open(self.path("/etc/inittab"), "rw+")
238 buf = f.read()
239 buf = buf.replace("id:3:initdefault", "id:5:initdefault")
240 f.seek(0)
241 f.write(buf)
242 f.close()
243 if ksxconfig.defaultdesktop:
244 f = open(self.path("/etc/sysconfig/desktop"), "w")
245 f.write("DESKTOP="+ksxconfig.defaultdesktop+"\n")
246 f.close()
248 class RPMMacroConfig(KickstartConfig):
249 """A class to apply the specified rpm macros to the filesystem"""
250 def apply(self, ks):
251 if not ks:
252 return
253 f = open(self.path("/etc/rpm/macros.imgcreate"), "w+")
254 if exclude_docs(ks):
255 f.write("%_excludedocs 1\n")
256 if not selinux_enabled(ks):
257 f.write("%__file_context_path %{nil}\n")
258 if inst_langs(ks) != None:
259 f.write("%_install_langs ")
260 f.write(inst_langs(ks))
261 f.write("\n")
262 f.close()
264 class NetworkConfig(KickstartConfig):
265 """A class to apply a kickstart network configuration to a system."""
266 def write_ifcfg(self, network):
267 p = self.path("/etc/sysconfig/network-scripts/ifcfg-" + network.device)
269 f = file(p, "w+")
270 os.chmod(p, 0644)
272 f.write("DEVICE=%s\n" % network.device)
273 f.write("BOOTPROTO=%s\n" % network.bootProto)
275 if network.bootProto.lower() == "static":
276 if network.ip:
277 f.write("IPADDR=%s\n" % network.ip)
278 if network.netmask:
279 f.write("NETMASK=%s\n" % network.netmask)
281 if network.onboot:
282 f.write("ONBOOT=on\n")
283 else:
284 f.write("ONBOOT=off\n")
286 if network.essid:
287 f.write("ESSID=%s\n" % network.essid)
289 if network.ethtool:
290 if network.ethtool.find("autoneg") == -1:
291 network.ethtool = "autoneg off " + network.ethtool
292 f.write("ETHTOOL_OPTS=%s\n" % network.ethtool)
294 if network.bootProto.lower() == "dhcp":
295 if network.hostname:
296 f.write("DHCP_HOSTNAME=%s\n" % network.hostname)
297 if network.dhcpclass:
298 f.write("DHCP_CLASSID=%s\n" % network.dhcpclass)
300 if network.mtu:
301 f.write("MTU=%s\n" % network.mtu)
303 f.close()
305 def write_wepkey(self, network):
306 if not network.wepkey:
307 return
309 p = self.path("/etc/sysconfig/network-scripts/keys-" + network.device)
310 f = file(p, "w+")
311 os.chmod(p, 0600)
312 f.write("KEY=%s\n" % network.wepkey)
313 f.close()
315 def write_sysconfig(self, useipv6, hostname, gateway):
316 path = self.path("/etc/sysconfig/network")
317 f = file(path, "w+")
318 os.chmod(path, 0644)
320 f.write("NETWORKING=yes\n")
322 if useipv6:
323 f.write("NETWORKING_IPV6=yes\n")
324 else:
325 f.write("NETWORKING_IPV6=no\n")
327 if hostname:
328 f.write("HOSTNAME=%s\n" % hostname)
329 else:
330 f.write("HOSTNAME=localhost.localdomain\n")
332 if gateway:
333 f.write("GATEWAY=%s\n" % gateway)
335 f.close()
337 def write_hosts(self, hostname):
338 localline = ""
339 if hostname and hostname != "localhost.localdomain":
340 localline += hostname + " "
341 l = hostname.split(".")
342 if len(l) > 1:
343 localline += l[0] + " "
344 localline += "localhost.localdomain localhost"
346 path = self.path("/etc/hosts")
347 f = file(path, "w+")
348 os.chmod(path, 0644)
349 f.write("127.0.0.1\t\t%s\n" % localline)
350 f.write("::1\t\tlocalhost6.localdomain6 localhost6\n")
351 f.close()
353 def write_resolv(self, nodns, nameservers):
354 if nodns or not nameservers:
355 return
357 path = self.path("/etc/resolv.conf")
358 f = file(path, "w+")
359 os.chmod(path, 0644)
361 for ns in (nameservers):
362 if ns:
363 f.write("nameserver %s\n" % ns)
365 f.close()
367 def apply(self, ksnet):
368 fs.makedirs(self.path("/etc/sysconfig/network-scripts"))
370 useipv6 = False
371 nodns = False
372 hostname = None
373 gateway = None
374 nameservers = None
376 for network in ksnet.network:
377 if not network.device:
378 raise errors.KickstartError("No --device specified with "
379 "network kickstart command")
381 if (network.onboot and network.bootProto.lower() != "dhcp" and
382 not (network.ip and network.netmask)):
383 raise errors.KickstartError("No IP address and/or netmask "
384 "specified with static "
385 "configuration for '%s'" %
386 network.device)
388 self.write_ifcfg(network)
389 self.write_wepkey(network)
391 if network.ipv6:
392 useipv6 = True
393 if network.nodns:
394 nodns = True
396 if network.hostname:
397 hostname = network.hostname
398 if network.gateway:
399 gateway = network.gateway
401 if network.nameserver:
402 nameservers = network.nameserver.split(",")
404 self.write_sysconfig(useipv6, hostname, gateway)
405 self.write_hosts(hostname)
406 self.write_resolv(nodns, nameservers)
408 class SelinuxConfig(KickstartConfig):
409 """A class to apply a kickstart selinux configuration to a system."""
410 def relabel(self, ksselinux):
411 # touch some files which get unhappy if they're not labeled correctly
412 for fn in ("/etc/resolv.conf",):
413 path = self.path(fn)
414 f = file(path, "w+")
415 os.chmod(path, 0644)
416 f.close()
418 if ksselinux.selinux == ksconstants.SELINUX_DISABLED:
419 return
421 if not os.path.exists(self.path("/sbin/setfiles")):
422 return
424 self.call(["/sbin/setfiles", "-p", "-e", "/proc", "-e", "/sys", "-e", "/dev", selinux.selinux_file_context_path(), "/"])
426 def apply(self, ksselinux):
427 if os.path.exists(self.path("/usr/sbin/lokkit")):
428 args = ["/usr/sbin/lokkit", "--quiet", "--nostart"]
430 if ksselinux.selinux == ksconstants.SELINUX_ENFORCING:
431 args.append("--selinux=enforcing")
432 if ksselinux.selinux == ksconstants.SELINUX_PERMISSIVE:
433 args.append("--selinux=permissive")
434 if ksselinux.selinux == ksconstants.SELINUX_DISABLED:
435 args.append("--selinux=disabled")
437 self.call(args)
439 self.relabel(ksselinux)
441 def get_image_size(ks, default = None):
442 __size = 0
443 for p in ks.handler.partition.partitions:
444 if p.mountpoint == "/" and p.size:
445 __size = p.size
446 if __size > 0:
447 return int(__size) * 1024L * 1024L
448 else:
449 return default
451 def get_image_fstype(ks, default = None):
452 for p in ks.handler.partition.partitions:
453 if p.mountpoint == "/" and p.fstype:
454 return p.fstype
455 return default
457 def get_modules(ks):
458 devices = []
459 if not hasattr(ks.handler.device, "deviceList"):
460 devices.append(ks.handler.device)
461 else:
462 devices.extend(ks.handler.device.deviceList)
464 modules = []
465 for device in devices:
466 if not device.moduleName:
467 continue
468 modules.extend(device.moduleName.split(":"))
470 return modules
472 def get_timeout(ks, default = None):
473 if not hasattr(ks.handler.bootloader, "timeout"):
474 return default
475 if ks.handler.bootloader.timeout is None:
476 return default
477 return int(ks.handler.bootloader.timeout)
479 def get_kernel_args(ks, default = "ro liveimg quiet"):
480 if not hasattr(ks.handler.bootloader, "appendLine"):
481 return default
482 if ks.handler.bootloader.appendLine is None:
483 return default
484 return "%s %s" %(default, ks.handler.bootloader.appendLine)
486 def get_default_kernel(ks, default = None):
487 if not hasattr(ks.handler.bootloader, "default"):
488 return default
489 if not ks.handler.bootloader.default:
490 return default
491 return ks.handler.bootloader.default
493 def get_repos(ks, repo_urls = {}):
494 repos = {}
495 for repo in ks.handler.repo.repoList:
496 inc = []
497 if hasattr(repo, "includepkgs"):
498 inc.extend(repo.includepkgs)
500 exc = []
501 if hasattr(repo, "excludepkgs"):
502 exc.extend(repo.excludepkgs)
504 baseurl = repo.baseurl
505 mirrorlist = repo.mirrorlist
506 proxy = repo.proxy
508 if repo.name in repo_urls:
509 baseurl = repo_urls[repo.name]
510 mirrorlist = None
512 if repos.has_key(repo.name):
513 logging.warn("Overriding already specified repo %s" %(repo.name,))
514 repos[repo.name] = (repo.name, baseurl, mirrorlist, proxy, inc, exc, repo.cost)
516 return repos.values()
518 def convert_method_to_repo(ks):
519 try:
520 ks.handler.repo.methodToRepo()
521 except (AttributeError, kserrors.KickstartError):
522 pass
524 def get_packages(ks, required = []):
525 return ks.handler.packages.packageList + required
527 def get_groups(ks, required = []):
528 return ks.handler.packages.groupList + required
530 def get_excluded(ks, required = []):
531 return ks.handler.packages.excludedList + required
533 def get_partitions(ks, required = []):
534 return ks.handler.partition.partitions
536 def ignore_missing(ks):
537 return ks.handler.packages.handleMissing == ksconstants.KS_MISSING_IGNORE
539 def exclude_docs(ks):
540 return ks.handler.packages.excludeDocs
542 def inst_langs(ks):
543 if hasattr(ks.handler.packages, "instLange"):
544 return ks.handler.packages.instLange
545 elif hasattr(ks.handler.packages, "instLangs"):
546 return ks.handler.packages.instLangs
547 return ""
549 def get_pre_scripts(ks):
550 scripts = []
551 for s in ks.handler.scripts:
552 if s.type != ksparser.KS_SCRIPT_PRE:
553 continue
554 scripts.append(s)
555 return scripts
557 def get_post_scripts(ks):
558 scripts = []
559 for s in ks.handler.scripts:
560 if s.type != ksparser.KS_SCRIPT_POST:
561 continue
562 scripts.append(s)
563 return scripts
565 def selinux_enabled(ks):
566 return ks.handler.selinux.selinux == ksconstants.SELINUX_ENFORCING