use locale.conf not sysconfig/i18n (#870805)
[livecd.git] / imgcreate / kickstart.py
blob1d8f5cf665a1266379d2157789c8c3f2876d2396
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/locale.conf"), "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 # /etc/localtime is a symlink with glibc > 2.15-41
153 if os.path.islink(self.path("/etc/localtime")):
154 os.unlink(self.path("/etc/localtime"))
155 os.symlink("/usr/share/zoneinfo/%s" %(tz,),
156 self.path("/etc/localtime"))
157 else:
158 try:
159 shutil.copy2(self.path("/usr/share/zoneinfo/%s" %(tz,)),
160 self.path("/etc/localtime"))
161 except (OSError, shutil.Error) as e:
162 log.error("Error copying timezone: %s" %(e.strerror,))
165 class AuthConfig(KickstartConfig):
166 """A class to apply a kickstart authconfig configuration to a system."""
167 def apply(self, ksauthconfig):
168 if not os.path.exists(self.path("/usr/sbin/authconfig")):
169 return
171 auth = ksauthconfig.authconfig or "--useshadow --enablemd5"
172 args = ["/usr/sbin/authconfig", "--update", "--nostart"]
173 self.call(args + auth.split())
175 class FirewallConfig(KickstartConfig):
176 """A class to apply a kickstart firewall configuration to a system."""
177 def apply(self, ksfirewall):
178 if not os.path.exists(self.path("/usr/sbin/lokkit")):
179 return
180 args = ["/usr/sbin/lokkit", "-f", "--quiet", "--nostart"]
181 if ksfirewall.enabled:
182 args.append("--enabled")
184 for port in ksfirewall.ports:
185 args.append("--port=%s" %(port,))
186 for svc in ksfirewall.services:
187 args.append("--service=%s" %(svc,))
188 for dev in ksfirewall.trusts:
189 args.append("--trust=%s" %(dev,))
190 else:
191 args.append("--disabled")
193 self.call(args)
195 class RootPasswordConfig(KickstartConfig):
196 """A class to apply a kickstart root password configuration to a system."""
197 def unset(self):
198 self.call(["/usr/bin/passwd", "-d", "root"])
200 def set_encrypted(self, password):
201 self.call(["/usr/sbin/usermod", "-p", password, "root"])
203 def set_unencrypted(self, password):
204 for p in ("/bin/echo", "/usr/bin/passwd"):
205 if not os.path.exists("%s/%s" %(self.instroot, p)):
206 raise errors.KickstartError("Unable to set unencrypted password due to lack of %s" % p)
208 p1 = subprocess.Popen(["/bin/echo", password],
209 stdout = subprocess.PIPE,
210 preexec_fn = self.chroot)
211 p2 = subprocess.Popen(["/usr/bin/passwd", "--stdin", "root"],
212 stdin = p1.stdout,
213 stdout = subprocess.PIPE,
214 preexec_fn = self.chroot)
215 p2.communicate()
217 def apply(self, ksrootpw):
218 if ksrootpw.isCrypted:
219 self.set_encrypted(ksrootpw.password)
220 elif ksrootpw.password != "":
221 self.set_unencrypted(ksrootpw.password)
222 else:
223 self.unset()
225 class ServicesConfig(KickstartConfig):
226 """A class to apply a kickstart services configuration to a system."""
227 def apply(self, ksservices):
228 if not os.path.exists(self.path("/sbin/chkconfig")):
229 return
230 for s in ksservices.enabled:
231 self.call(["/sbin/chkconfig", s, "on"])
232 for s in ksservices.disabled:
233 self.call(["/sbin/chkconfig", s, "off"])
235 class XConfig(KickstartConfig):
236 """A class to apply a kickstart X configuration to a system."""
237 def apply(self, ksxconfig):
238 if ksxconfig.startX:
239 f = open(self.path("/etc/inittab"), "rw+")
240 buf = f.read()
241 buf = buf.replace("id:3:initdefault", "id:5:initdefault")
242 f.seek(0)
243 f.write(buf)
244 f.close()
245 if ksxconfig.defaultdesktop:
246 f = open(self.path("/etc/sysconfig/desktop"), "w")
247 f.write("DESKTOP="+ksxconfig.defaultdesktop+"\n")
248 f.close()
250 class RPMMacroConfig(KickstartConfig):
251 """A class to apply the specified rpm macros to the filesystem"""
252 def apply(self, ks):
253 if not ks:
254 return
255 f = open(self.path("/etc/rpm/macros.imgcreate"), "w+")
256 if exclude_docs(ks):
257 f.write("%_excludedocs 1\n")
258 if not selinux_enabled(ks):
259 f.write("%__file_context_path %{nil}\n")
260 if inst_langs(ks) != None:
261 f.write("%_install_langs ")
262 f.write(inst_langs(ks))
263 f.write("\n")
264 f.close()
266 class NetworkConfig(KickstartConfig):
267 """A class to apply a kickstart network configuration to a system."""
268 def write_ifcfg(self, network):
269 p = self.path("/etc/sysconfig/network-scripts/ifcfg-" + network.device)
271 f = file(p, "w+")
272 os.chmod(p, 0644)
274 f.write("DEVICE=%s\n" % network.device)
275 f.write("BOOTPROTO=%s\n" % network.bootProto)
277 if network.bootProto.lower() == "static":
278 if network.ip:
279 f.write("IPADDR=%s\n" % network.ip)
280 if network.netmask:
281 f.write("NETMASK=%s\n" % network.netmask)
283 if network.onboot:
284 f.write("ONBOOT=on\n")
285 else:
286 f.write("ONBOOT=off\n")
288 if network.essid:
289 f.write("ESSID=%s\n" % network.essid)
291 if network.ethtool:
292 if network.ethtool.find("autoneg") == -1:
293 network.ethtool = "autoneg off " + network.ethtool
294 f.write("ETHTOOL_OPTS=%s\n" % network.ethtool)
296 if network.bootProto.lower() == "dhcp":
297 if network.hostname:
298 f.write("DHCP_HOSTNAME=%s\n" % network.hostname)
299 if network.dhcpclass:
300 f.write("DHCP_CLASSID=%s\n" % network.dhcpclass)
302 if network.mtu:
303 f.write("MTU=%s\n" % network.mtu)
305 f.close()
307 def write_wepkey(self, network):
308 if not network.wepkey:
309 return
311 p = self.path("/etc/sysconfig/network-scripts/keys-" + network.device)
312 f = file(p, "w+")
313 os.chmod(p, 0600)
314 f.write("KEY=%s\n" % network.wepkey)
315 f.close()
317 def write_sysconfig(self, useipv6, hostname, gateway):
318 path = self.path("/etc/sysconfig/network")
319 f = file(path, "w+")
320 os.chmod(path, 0644)
322 f.write("NETWORKING=yes\n")
324 if useipv6:
325 f.write("NETWORKING_IPV6=yes\n")
326 else:
327 f.write("NETWORKING_IPV6=no\n")
329 if hostname:
330 f.write("HOSTNAME=%s\n" % hostname)
331 else:
332 f.write("HOSTNAME=localhost.localdomain\n")
334 if gateway:
335 f.write("GATEWAY=%s\n" % gateway)
337 f.close()
339 def write_hosts(self, hostname):
340 localline = ""
341 if hostname and hostname != "localhost.localdomain":
342 localline += hostname + " "
343 l = hostname.split(".")
344 if len(l) > 1:
345 localline += l[0] + " "
346 localline += "localhost.localdomain localhost"
348 path = self.path("/etc/hosts")
349 f = file(path, "w+")
350 os.chmod(path, 0644)
351 f.write("127.0.0.1\t\t%s\n" % localline)
352 f.write("::1\t\tlocalhost6.localdomain6 localhost6\n")
353 f.close()
355 def write_resolv(self, nodns, nameservers):
356 if nodns or not nameservers:
357 return
359 path = self.path("/etc/resolv.conf")
360 f = file(path, "w+")
361 os.chmod(path, 0644)
363 for ns in (nameservers):
364 if ns:
365 f.write("nameserver %s\n" % ns)
367 f.close()
369 def apply(self, ksnet):
370 fs.makedirs(self.path("/etc/sysconfig/network-scripts"))
372 useipv6 = False
373 nodns = False
374 hostname = None
375 gateway = None
376 nameservers = None
378 for network in ksnet.network:
379 if not network.device:
380 raise errors.KickstartError("No --device specified with "
381 "network kickstart command")
383 if (network.onboot and network.bootProto.lower() != "dhcp" and
384 not (network.ip and network.netmask)):
385 raise errors.KickstartError("No IP address and/or netmask "
386 "specified with static "
387 "configuration for '%s'" %
388 network.device)
390 self.write_ifcfg(network)
391 self.write_wepkey(network)
393 if network.ipv6:
394 useipv6 = True
395 if network.nodns:
396 nodns = True
398 if network.hostname:
399 hostname = network.hostname
400 if network.gateway:
401 gateway = network.gateway
403 if network.nameserver:
404 nameservers = network.nameserver.split(",")
406 self.write_sysconfig(useipv6, hostname, gateway)
407 self.write_hosts(hostname)
408 self.write_resolv(nodns, nameservers)
410 class SelinuxConfig(KickstartConfig):
411 """A class to apply a kickstart selinux configuration to a system."""
412 def relabel(self, ksselinux):
413 # touch some files which get unhappy if they're not labeled correctly
414 for fn in ("/etc/resolv.conf",):
415 path = self.path(fn)
416 f = file(path, "w+")
417 os.chmod(path, 0644)
418 f.close()
420 if ksselinux.selinux == ksconstants.SELINUX_DISABLED:
421 return
423 if not os.path.exists(self.path("/sbin/setfiles")):
424 return
426 self.call(["/sbin/setfiles", "-p", "-e", "/proc", "-e", "/sys", "-e", "/dev", selinux.selinux_file_context_path(), "/"])
428 def apply(self, ksselinux):
429 if os.path.exists(self.path("/usr/sbin/lokkit")):
430 args = ["/usr/sbin/lokkit", "--quiet", "--nostart"]
432 if ksselinux.selinux == ksconstants.SELINUX_ENFORCING:
433 args.append("--selinux=enforcing")
434 if ksselinux.selinux == ksconstants.SELINUX_PERMISSIVE:
435 args.append("--selinux=permissive")
436 if ksselinux.selinux == ksconstants.SELINUX_DISABLED:
437 args.append("--selinux=disabled")
439 self.call(args)
441 self.relabel(ksselinux)
443 def get_image_size(ks, default = None):
444 __size = 0
445 for p in ks.handler.partition.partitions:
446 if p.mountpoint == "/" and p.size:
447 __size = p.size
448 if __size > 0:
449 return int(__size) * 1024L * 1024L
450 else:
451 return default
453 def get_image_fstype(ks, default = None):
454 for p in ks.handler.partition.partitions:
455 if p.mountpoint == "/" and p.fstype:
456 return p.fstype
457 return default
459 def get_modules(ks):
460 devices = []
461 if not hasattr(ks.handler.device, "deviceList"):
462 devices.append(ks.handler.device)
463 else:
464 devices.extend(ks.handler.device.deviceList)
466 modules = []
467 for device in devices:
468 if not device.moduleName:
469 continue
470 modules.extend(device.moduleName.split(":"))
472 return modules
474 def get_timeout(ks, default = None):
475 if not hasattr(ks.handler.bootloader, "timeout"):
476 return default
477 if ks.handler.bootloader.timeout is None:
478 return default
479 return int(ks.handler.bootloader.timeout)
481 def get_kernel_args(ks, default = "ro rd.live.image quiet"):
482 if not hasattr(ks.handler.bootloader, "appendLine"):
483 return default
484 if ks.handler.bootloader.appendLine is None:
485 return default
486 return "%s %s" %(default, ks.handler.bootloader.appendLine)
488 def get_default_kernel(ks, default = None):
489 if not hasattr(ks.handler.bootloader, "default"):
490 return default
491 if not ks.handler.bootloader.default:
492 return default
493 return ks.handler.bootloader.default
495 def get_repos(ks, repo_urls = {}):
496 repos = {}
497 for repo in ks.handler.repo.repoList:
498 inc = []
499 if hasattr(repo, "includepkgs"):
500 inc.extend(repo.includepkgs)
502 exc = []
503 if hasattr(repo, "excludepkgs"):
504 exc.extend(repo.excludepkgs)
506 baseurl = repo.baseurl
507 mirrorlist = repo.mirrorlist
508 proxy = repo.proxy
510 if repo.name in repo_urls:
511 baseurl = repo_urls[repo.name]
512 mirrorlist = None
514 if repos.has_key(repo.name):
515 logging.warn("Overriding already specified repo %s" %(repo.name,))
516 repos[repo.name] = (repo.name, baseurl, mirrorlist, proxy, inc, exc, repo.cost)
518 return repos.values()
520 def convert_method_to_repo(ks):
521 try:
522 ks.handler.repo.methodToRepo()
523 except (AttributeError, kserrors.KickstartError):
524 pass
526 def get_packages(ks, required = []):
527 return ks.handler.packages.packageList + required
529 def get_groups(ks, required = []):
530 return ks.handler.packages.groupList + required
532 def get_excluded(ks, required = []):
533 return ks.handler.packages.excludedList + required
535 def get_partitions(ks, required = []):
536 return ks.handler.partition.partitions
538 def ignore_missing(ks):
539 return ks.handler.packages.handleMissing == ksconstants.KS_MISSING_IGNORE
541 def exclude_docs(ks):
542 return ks.handler.packages.excludeDocs
544 def inst_langs(ks):
545 if hasattr(ks.handler.packages, "instLange"):
546 return ks.handler.packages.instLange
547 elif hasattr(ks.handler.packages, "instLangs"):
548 return ks.handler.packages.instLangs
549 return ""
551 def get_pre_scripts(ks):
552 scripts = []
553 for s in ks.handler.scripts:
554 if s.type != ksparser.KS_SCRIPT_PRE:
555 continue
556 scripts.append(s)
557 return scripts
559 def get_post_scripts(ks):
560 scripts = []
561 for s in ks.handler.scripts:
562 if s.type != ksparser.KS_SCRIPT_POST:
563 continue
564 scripts.append(s)
565 return scripts
567 def selinux_enabled(ks):
568 return ks.handler.selinux.selinux == ksconstants.SELINUX_ENFORCING