set selinux permissive mode when building
[livecd.git] / imgcreate / kickstart.py
blobe3d4697a7ca2aac4931b474ba836982bb962595a
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 logging.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 args = ["/usr/bin/firewall-offline-cmd"]
179 # enabled is None if neither --enable or --disable is passed
180 # default to enabled if nothing has been set.
181 if ksfirewall.enabled == False:
182 args += ["--disabled"]
183 else:
184 args += ["--enabled"]
186 for dev in ksfirewall.trusts:
187 args += [ "--trust=%s" % (dev,) ]
189 for port in ksfirewall.ports:
190 args += [ "--port=%s" % (port,) ]
192 for service in ksfirewall.services:
193 args += [ "--service=%s" % (service,) ]
195 self.call(args)
197 class RootPasswordConfig(KickstartConfig):
198 """A class to apply a kickstart root password configuration to a system."""
199 def unset(self):
200 self.call(["/usr/bin/passwd", "-d", "root"])
202 def set_encrypted(self, password):
203 self.call(["/usr/sbin/usermod", "-p", password, "root"])
205 def set_unencrypted(self, password):
206 for p in ("/bin/echo", "/usr/bin/passwd"):
207 if not os.path.exists("%s/%s" %(self.instroot, p)):
208 raise errors.KickstartError("Unable to set unencrypted password due to lack of %s" % p)
210 p1 = subprocess.Popen(["/bin/echo", password],
211 stdout = subprocess.PIPE,
212 preexec_fn = self.chroot)
213 p2 = subprocess.Popen(["/usr/bin/passwd", "--stdin", "root"],
214 stdin = p1.stdout,
215 stdout = subprocess.PIPE,
216 preexec_fn = self.chroot)
217 p2.communicate()
219 def apply(self, ksrootpw):
220 if ksrootpw.isCrypted:
221 self.set_encrypted(ksrootpw.password)
222 elif ksrootpw.password != "":
223 self.set_unencrypted(ksrootpw.password)
224 else:
225 self.unset()
227 class ServicesConfig(KickstartConfig):
228 """A class to apply a kickstart services configuration to a system."""
229 def apply(self, ksservices):
230 if not os.path.exists(self.path("/sbin/chkconfig")):
231 return
232 for s in ksservices.enabled:
233 self.call(["/sbin/chkconfig", s, "on"])
234 for s in ksservices.disabled:
235 self.call(["/sbin/chkconfig", s, "off"])
237 class XConfig(KickstartConfig):
238 """A class to apply a kickstart X configuration to a system."""
239 def apply(self, ksxconfig):
240 if ksxconfig.startX:
241 f = open(self.path("/etc/inittab"), "rw+")
242 buf = f.read()
243 buf = buf.replace("id:3:initdefault", "id:5:initdefault")
244 f.seek(0)
245 f.write(buf)
246 f.close()
247 if ksxconfig.defaultdesktop:
248 f = open(self.path("/etc/sysconfig/desktop"), "w")
249 f.write("DESKTOP="+ksxconfig.defaultdesktop+"\n")
250 f.close()
252 class RPMMacroConfig(KickstartConfig):
253 """A class to apply the specified rpm macros to the filesystem"""
254 def apply(self, ks):
255 if not ks:
256 return
257 f = open(self.path("/etc/rpm/macros.imgcreate"), "w+")
258 if exclude_docs(ks):
259 f.write("%_excludedocs 1\n")
260 if not selinux_enabled(ks):
261 f.write("%__file_context_path %{nil}\n")
262 if inst_langs(ks) != None:
263 f.write("%_install_langs ")
264 f.write(inst_langs(ks))
265 f.write("\n")
266 f.close()
268 class NetworkConfig(KickstartConfig):
269 """A class to apply a kickstart network configuration to a system."""
270 def write_ifcfg(self, network):
271 p = self.path("/etc/sysconfig/network-scripts/ifcfg-" + network.device)
273 f = file(p, "w+")
274 os.chmod(p, 0644)
276 f.write("DEVICE=%s\n" % network.device)
277 f.write("BOOTPROTO=%s\n" % network.bootProto)
279 if network.bootProto.lower() == "static":
280 if network.ip:
281 f.write("IPADDR=%s\n" % network.ip)
282 if network.netmask:
283 f.write("NETMASK=%s\n" % network.netmask)
285 if network.onboot:
286 f.write("ONBOOT=on\n")
287 else:
288 f.write("ONBOOT=off\n")
290 if network.essid:
291 f.write("ESSID=%s\n" % network.essid)
293 if network.ethtool:
294 if network.ethtool.find("autoneg") == -1:
295 network.ethtool = "autoneg off " + network.ethtool
296 f.write("ETHTOOL_OPTS=%s\n" % network.ethtool)
298 if network.bootProto.lower() == "dhcp":
299 if network.hostname:
300 f.write("DHCP_HOSTNAME=%s\n" % network.hostname)
301 if network.dhcpclass:
302 f.write("DHCP_CLASSID=%s\n" % network.dhcpclass)
304 if network.mtu:
305 f.write("MTU=%s\n" % network.mtu)
307 f.close()
309 def write_wepkey(self, network):
310 if not network.wepkey:
311 return
313 p = self.path("/etc/sysconfig/network-scripts/keys-" + network.device)
314 f = file(p, "w+")
315 os.chmod(p, 0600)
316 f.write("KEY=%s\n" % network.wepkey)
317 f.close()
319 def write_sysconfig(self, useipv6, hostname, gateway):
320 path = self.path("/etc/sysconfig/network")
321 f = file(path, "w+")
322 os.chmod(path, 0644)
324 f.write("NETWORKING=yes\n")
326 if useipv6:
327 f.write("NETWORKING_IPV6=yes\n")
328 else:
329 f.write("NETWORKING_IPV6=no\n")
331 if gateway:
332 f.write("GATEWAY=%s\n" % gateway)
334 f.close()
336 def write_hosts(self, hostname):
337 localline = ""
338 if hostname and hostname != "localhost.localdomain":
339 localline += hostname + " "
340 l = hostname.split(".")
341 if len(l) > 1:
342 localline += l[0] + " "
343 localline += "localhost.localdomain localhost"
345 path = self.path("/etc/hosts")
346 f = file(path, "w+")
347 os.chmod(path, 0644)
348 f.write("127.0.0.1\t\t%s\n" % localline)
349 f.write("::1\t\tlocalhost6.localdomain6 localhost6\n")
350 f.close()
352 def write_hostname(self, hostname):
353 if not hostname:
354 return
356 path = self.path("/etc/hostname")
357 f = file(path, "w+")
358 os.chmod(path, 0644)
359 f.write("%s\n" % (hostname,))
360 f.close()
362 def write_resolv(self, nodns, nameservers):
363 if nodns or not nameservers:
364 return
366 path = self.path("/etc/resolv.conf")
367 f = file(path, "w+")
368 os.chmod(path, 0644)
370 for ns in (nameservers):
371 if ns:
372 f.write("nameserver %s\n" % ns)
374 f.close()
376 def apply(self, ksnet):
377 fs.makedirs(self.path("/etc/sysconfig/network-scripts"))
379 useipv6 = False
380 nodns = False
381 hostname = None
382 gateway = None
383 nameservers = None
385 for network in ksnet.network:
386 if not network.device:
387 raise errors.KickstartError("No --device specified with "
388 "network kickstart command")
390 if (network.onboot and network.bootProto.lower() != "dhcp" and
391 not (network.ip and network.netmask)):
392 raise errors.KickstartError("No IP address and/or netmask "
393 "specified with static "
394 "configuration for '%s'" %
395 network.device)
397 self.write_ifcfg(network)
398 self.write_wepkey(network)
400 if network.ipv6:
401 useipv6 = True
402 if network.nodns:
403 nodns = True
405 if network.hostname:
406 hostname = network.hostname
407 if network.gateway:
408 gateway = network.gateway
410 if network.nameserver:
411 nameservers = network.nameserver.split(",")
413 self.write_sysconfig(useipv6, hostname, gateway)
414 self.write_hosts(hostname)
415 self.write_hostname(hostname)
416 self.write_resolv(nodns, nameservers)
418 class SelinuxConfig(KickstartConfig):
419 """A class to apply a kickstart selinux configuration to a system."""
420 def relabel(self, ksselinux):
421 # touch some files which get unhappy if they're not labeled correctly
422 for fn in ("/etc/resolv.conf",):
423 path = self.path(fn)
424 f = file(path, "w+")
425 os.chmod(path, 0644)
426 f.close()
428 if ksselinux.selinux == ksconstants.SELINUX_DISABLED:
429 return
431 if not os.path.exists(self.path("/sbin/setfiles")):
432 return
434 self.call(["/sbin/setfiles", "-p", "-e", "/proc", "-e", "/sys", "-e", "/dev", selinux.selinux_file_context_path(), "/"])
436 def apply(self, ksselinux):
437 selinux_config = "/etc/selinux/config"
438 if not os.path.exists(self.instroot+selinux_config):
439 return
441 if ksselinux.selinux == ksconstants.SELINUX_ENFORCING:
442 cmd = "SELINUX=enforcing\n"
443 elif ksselinux.selinux == ksconstants.SELINUX_PERMISSIVE:
444 cmd = "SELINUX=permissive\n"
445 elif ksselinux.selinux == ksconstants.SELINUX_DISABLED:
446 cmd = "SELINUX=disabled\n"
447 else:
448 return
450 # Replace the SELINUX line in the config
451 lines = open(self.instroot+selinux_config).readlines()
452 with open(self.instroot+selinux_config, "w") as f:
453 for line in lines:
454 if line.startswith("SELINUX="):
455 f.write(cmd)
456 else:
457 f.write(line)
459 self.relabel(ksselinux)
461 def get_image_size(ks, default = None):
462 __size = 0
463 for p in ks.handler.partition.partitions:
464 if p.mountpoint == "/" and p.size:
465 __size = p.size
466 if __size > 0:
467 return int(__size) * 1024L * 1024L
468 else:
469 return default
471 def get_image_fstype(ks, default = None):
472 for p in ks.handler.partition.partitions:
473 if p.mountpoint == "/" and p.fstype:
474 return p.fstype
475 return default
477 def get_modules(ks):
478 devices = []
479 if not hasattr(ks.handler.device, "deviceList"):
480 devices.append(ks.handler.device)
481 else:
482 devices.extend(ks.handler.device.deviceList)
484 modules = []
485 for device in devices:
486 if not device.moduleName:
487 continue
488 modules.extend(device.moduleName.split(":"))
490 return modules
492 def get_timeout(ks, default = None):
493 if not hasattr(ks.handler.bootloader, "timeout"):
494 return default
495 if ks.handler.bootloader.timeout is None:
496 return default
497 return int(ks.handler.bootloader.timeout)
499 def get_kernel_args(ks, default = "ro rd.live.image quiet"):
500 if not hasattr(ks.handler.bootloader, "appendLine"):
501 return default
502 if ks.handler.bootloader.appendLine is None:
503 return default
504 return "%s %s" %(default, ks.handler.bootloader.appendLine)
506 def get_default_kernel(ks, default = None):
507 if not hasattr(ks.handler.bootloader, "default"):
508 return default
509 if not ks.handler.bootloader.default:
510 return default
511 return ks.handler.bootloader.default
513 def get_repos(ks, repo_urls = {}):
514 repos = {}
515 for repo in ks.handler.repo.repoList:
516 inc = []
517 if hasattr(repo, "includepkgs"):
518 inc.extend(repo.includepkgs)
520 exc = []
521 if hasattr(repo, "excludepkgs"):
522 exc.extend(repo.excludepkgs)
524 baseurl = repo.baseurl
525 mirrorlist = repo.mirrorlist
526 proxy = repo.proxy
528 if repo.name in repo_urls:
529 baseurl = repo_urls[repo.name]
530 mirrorlist = None
532 if repos.has_key(repo.name):
533 logging.warn("Overriding already specified repo %s" %(repo.name,))
534 repos[repo.name] = (repo.name, baseurl, mirrorlist, proxy, inc, exc, repo.cost)
536 return repos.values()
538 def convert_method_to_repo(ks):
539 try:
540 ks.handler.repo.methodToRepo()
541 except (AttributeError, kserrors.KickstartError):
542 pass
544 def get_packages(ks, required = []):
545 return ks.handler.packages.packageList + required
547 def get_groups(ks, required = []):
548 return ks.handler.packages.groupList + required
550 def get_excluded(ks, required = []):
551 return ks.handler.packages.excludedList + required
553 def get_partitions(ks, required = []):
554 return ks.handler.partition.partitions
556 def ignore_missing(ks):
557 return ks.handler.packages.handleMissing == ksconstants.KS_MISSING_IGNORE
559 def exclude_docs(ks):
560 return ks.handler.packages.excludeDocs
562 def inst_langs(ks):
563 if hasattr(ks.handler.packages, "instLange"):
564 return ks.handler.packages.instLange
565 elif hasattr(ks.handler.packages, "instLangs"):
566 return ks.handler.packages.instLangs
567 return ""
569 def get_pre_scripts(ks):
570 scripts = []
571 for s in ks.handler.scripts:
572 if s.type != ksparser.KS_SCRIPT_PRE:
573 continue
574 scripts.append(s)
575 return scripts
577 def get_post_scripts(ks):
578 scripts = []
579 for s in ks.handler.scripts:
580 if s.type != ksparser.KS_SCRIPT_POST:
581 continue
582 scripts.append(s)
583 return scripts
585 def selinux_enabled(ks):
586 return ks.handler.selinux.selinux == ksconstants.SELINUX_ENFORCING