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.
28 import system_config_keyboard
.keyboard
as keyboard
30 import rhpl
.keyboard
as keyboard
32 import pykickstart
.commands
as kscommands
33 import pykickstart
.constants
as ksconstants
34 import pykickstart
.errors
as kserrors
35 import pykickstart
.parser
as ksparser
36 import pykickstart
.version
as ksversion
38 import imgcreate
.errors
as errors
39 import imgcreate
.fs
as fs
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 ksfile
= urlgrabber
.urlgrab(path
)
55 ks
.readKickstart(ksfile
)
56 # Fallback to e.args[0] is a workaround for bugs in urlgragger and pykickstart.
58 raise errors
.KickstartError("Failed to read kickstart file "
59 "'%s' : %s" % (path
, e
.strerror
or
61 except kserrors
.KickstartError
, e
:
62 raise errors
.KickstartError("Failed to parse kickstart file "
63 "'%s' : %s" % (path
, e
))
66 def build_name(kscfg
, prefix
= None, suffix
= None, maxlen
= None):
67 """Construct and return an image name string.
69 This is a utility function to help create sensible name and fslabel
70 strings. The name is constructed using the sans-prefix-and-extension
71 kickstart filename and the supplied prefix and suffix.
73 If the name exceeds the maxlen length supplied, the prefix is first dropped
74 and then the kickstart filename portion is reduced until it fits. In other
75 words, the suffix takes precedence over the kickstart portion and the
76 kickstart portion takes precedence over the prefix.
78 kscfg -- a path to a kickstart file
79 prefix -- a prefix to prepend to the name; defaults to None, which causes
81 suffix -- a suffix to append to the name; defaults to None, which causes
82 a YYYYMMDDHHMM suffix to be used
83 maxlen -- the maximum length for the returned string; defaults to None,
84 which means there is no restriction on the name length
86 Note, if maxlen is less then the len(suffix), you get to keep both pieces.
89 name
= os
.path
.basename(kscfg
)
97 suffix
= time
.strftime("%Y%m%d%H%M")
99 if name
.startswith(prefix
):
100 name
= name
[len(prefix
):]
102 ret
= prefix
+ name
+ "-" + suffix
103 if not maxlen
is None and len(ret
) > maxlen
:
104 ret
= name
[:maxlen
- len(suffix
) - 1] + "-" + suffix
108 class KickstartConfig(object):
109 """A base class for applying kickstart configurations to a system."""
110 def __init__(self
, instroot
):
111 self
.instroot
= instroot
113 def path(self
, subpath
):
114 return self
.instroot
+ subpath
117 os
.chroot(self
.instroot
)
120 def call(self
, args
):
121 if not os
.path
.exists("%s/%s" %(self
.instroot
, args
[0])):
122 raise errors
.KickstartError("Unable to run %s!" %(args))
123 subprocess
.call(args
, preexec_fn
= self
.chroot
)
128 class LanguageConfig(KickstartConfig
):
129 """A class to apply a kickstart language configuration to a system."""
130 def apply(self
, kslang
):
131 lang
= kslang
.lang
or "en_US.UTF-8"
133 f
= open(self
.path("/etc/sysconfig/i18n"), "w+")
134 f
.write("LANG=\"" + lang
+ "\"\n")
137 class KeyboardConfig(KickstartConfig
):
138 """A class to apply a kickstart keyboard configuration to a system."""
139 def apply(self
, kskeyboard
):
140 k
= keyboard
.Keyboard()
141 if kskeyboard
.keyboard
:
142 k
.set(kskeyboard
.keyboard
)
143 k
.write(self
.instroot
)
145 class TimezoneConfig(KickstartConfig
):
146 """A class to apply a kickstart timezone configuration to a system."""
147 def apply(self
, kstimezone
):
148 tz
= kstimezone
.timezone
or "America/New_York"
149 utc
= str(kstimezone
.isUtc
)
151 f
= open(self
.path("/etc/sysconfig/clock"), "w+")
152 f
.write("ZONE=\"" + tz
+ "\"\n")
153 f
.write("UTC=" + utc
+ "\n")
156 shutil
.copyfile(self
.path("/usr/share/zoneinfo/%s" %(tz
,)),
157 self
.path("/etc/localtime"))
159 log
.error("Error copying timezone: %s" %(e
.strerror
,))
162 class AuthConfig(KickstartConfig
):
163 """A class to apply a kickstart authconfig configuration to a system."""
164 def apply(self
, ksauthconfig
):
165 if not os
.path
.exists(self
.path("/usr/sbin/authconfig")):
168 auth
= ksauthconfig
.authconfig
or "--useshadow --enablemd5"
169 args
= ["/usr/sbin/authconfig", "--update", "--nostart"]
170 self
.call(args
+ auth
.split())
172 class FirewallConfig(KickstartConfig
):
173 """A class to apply a kickstart firewall configuration to a system."""
174 def apply(self
, ksfirewall
):
175 if not os
.path
.exists(self
.path("/usr/sbin/lokkit")):
177 args
= ["/usr/sbin/lokkit", "-f", "--quiet", "--nostart"]
178 if ksfirewall
.enabled
:
179 args
.append("--enabled")
181 for port
in ksfirewall
.ports
:
182 args
.append("--port=%s" %(port
,))
183 for svc
in ksfirewall
.services
:
184 args
.append("--service=%s" %(svc
,))
185 for dev
in ksfirewall
.trusts
:
186 args
.append("--trust=%s" %(dev
,))
188 args
.append("--disabled")
192 class RootPasswordConfig(KickstartConfig
):
193 """A class to apply a kickstart root password configuration to a system."""
195 self
.call(["/usr/bin/passwd", "-d", "root"])
197 def set_encrypted(self
, password
):
198 self
.call(["/usr/sbin/usermod", "-p", password
, "root"])
200 def set_unencrypted(self
, password
):
201 for p
in ("/bin/echo", "/usr/bin/passwd"):
202 if not os
.path
.exists("%s/%s" %(self
.instroot
, p
)):
203 raise errors
.KickstartError("Unable to set unencrypted password due to lack of %s" % p
)
205 p1
= subprocess
.Popen(["/bin/echo", password
],
206 stdout
= subprocess
.PIPE
,
207 preexec_fn
= self
.chroot
)
208 p2
= subprocess
.Popen(["/usr/bin/passwd", "--stdin", "root"],
210 stdout
= subprocess
.PIPE
,
211 preexec_fn
= self
.chroot
)
214 def apply(self
, ksrootpw
):
215 if ksrootpw
.isCrypted
:
216 self
.set_encrypted(ksrootpw
.password
)
217 elif ksrootpw
.password
!= "":
218 self
.set_unencrypted(ksrootpw
.password
)
222 class ServicesConfig(KickstartConfig
):
223 """A class to apply a kickstart services configuration to a system."""
224 def apply(self
, ksservices
):
225 if not os
.path
.exists(self
.path("/sbin/chkconfig")):
227 for s
in ksservices
.enabled
:
228 self
.call(["/sbin/chkconfig", s
, "on"])
229 for s
in ksservices
.disabled
:
230 self
.call(["/sbin/chkconfig", s
, "off"])
232 class XConfig(KickstartConfig
):
233 """A class to apply a kickstart X configuration to a system."""
234 def apply(self
, ksxconfig
):
236 f
= open(self
.path("/etc/inittab"), "rw+")
238 buf
= buf
.replace("id:3:initdefault", "id:5:initdefault")
242 if ksxconfig
.defaultdesktop
:
243 f
= open(self
.path("/etc/sysconfig/desktop"), "w")
244 f
.write("DESKTOP="+ksxconfig
.defaultdesktop
+"\n")
247 class RPMMacroConfig(KickstartConfig
):
248 """A class to apply the specified rpm macros to the filesystem"""
252 f
= open(self
.path("/etc/rpm/macros.imgcreate"), "w+")
254 f
.write("%_excludedocs 1\n")
255 if not selinux_enabled(ks
):
256 f
.write("%__file_context_path %{nil}\n")
257 if inst_langs(ks
) != None:
258 f
.write("%_install_langs ")
259 f
.write(inst_langs(ks
))
263 class NetworkConfig(KickstartConfig
):
264 """A class to apply a kickstart network configuration to a system."""
265 def write_ifcfg(self
, network
):
266 p
= self
.path("/etc/sysconfig/network-scripts/ifcfg-" + network
.device
)
271 f
.write("DEVICE=%s\n" % network
.device
)
272 f
.write("BOOTPROTO=%s\n" % network
.bootProto
)
274 if network
.bootProto
.lower() == "static":
276 f
.write("IPADDR=%s\n" % network
.ip
)
278 f
.write("NETMASK=%s\n" % network
.netmask
)
281 f
.write("ONBOOT=on\n")
283 f
.write("ONBOOT=off\n")
286 f
.write("ESSID=%s\n" % network
.essid
)
289 if network
.ethtool
.find("autoneg") == -1:
290 network
.ethtool
= "autoneg off " + network
.ethtool
291 f
.write("ETHTOOL_OPTS=%s\n" % network
.ethtool
)
293 if network
.bootProto
.lower() == "dhcp":
295 f
.write("DHCP_HOSTNAME=%s\n" % network
.hostname
)
296 if network
.dhcpclass
:
297 f
.write("DHCP_CLASSID=%s\n" % network
.dhcpclass
)
300 f
.write("MTU=%s\n" % network
.mtu
)
304 def write_wepkey(self
, network
):
305 if not network
.wepkey
:
308 p
= self
.path("/etc/sysconfig/network-scripts/keys-" + network
.device
)
311 f
.write("KEY=%s\n" % network
.wepkey
)
314 def write_sysconfig(self
, useipv6
, hostname
, gateway
):
315 path
= self
.path("/etc/sysconfig/network")
319 f
.write("NETWORKING=yes\n")
322 f
.write("NETWORKING_IPV6=yes\n")
324 f
.write("NETWORKING_IPV6=no\n")
327 f
.write("HOSTNAME=%s\n" % hostname
)
329 f
.write("HOSTNAME=localhost.localdomain\n")
332 f
.write("GATEWAY=%s\n" % gateway
)
336 def write_hosts(self
, hostname
):
338 if hostname
and hostname
!= "localhost.localdomain":
339 localline
+= hostname
+ " "
340 l
= hostname
.split(".")
342 localline
+= l
[0] + " "
343 localline
+= "localhost.localdomain localhost"
345 path
= self
.path("/etc/hosts")
348 f
.write("127.0.0.1\t\t%s\n" % localline
)
349 f
.write("::1\t\tlocalhost6.localdomain6 localhost6\n")
352 def write_resolv(self
, nodns
, nameservers
):
353 if nodns
or not nameservers
:
356 path
= self
.path("/etc/resolv.conf")
360 for ns
in (nameservers
):
362 f
.write("nameserver %s\n" % ns
)
366 def apply(self
, ksnet
):
367 fs
.makedirs(self
.path("/etc/sysconfig/network-scripts"))
375 for network
in ksnet
.network
:
376 if not network
.device
:
377 raise errors
.KickstartError("No --device specified with "
378 "network kickstart command")
380 if (network
.onboot
and network
.bootProto
.lower() != "dhcp" and
381 not (network
.ip
and network
.netmask
)):
382 raise errors
.KickstartError("No IP address and/or netmask "
383 "specified with static "
384 "configuration for '%s'" %
387 self
.write_ifcfg(network
)
388 self
.write_wepkey(network
)
396 hostname
= network
.hostname
398 gateway
= network
.gateway
400 if network
.nameserver
:
401 nameservers
= network
.nameserver
.split(",")
403 self
.write_sysconfig(useipv6
, hostname
, gateway
)
404 self
.write_hosts(hostname
)
405 self
.write_resolv(nodns
, nameservers
)
407 class SelinuxConfig(KickstartConfig
):
408 """A class to apply a kickstart selinux configuration to a system."""
409 def relabel(self
, ksselinux
):
410 # touch some files which get unhappy if they're not labeled correctly
411 for fn
in ("/etc/resolv.conf",):
417 if ksselinux
.selinux
== ksconstants
.SELINUX_DISABLED
:
420 if not os
.path
.exists(self
.path("/sbin/setfiles")):
423 self
.call(["/sbin/setfiles", "-e", "/proc", "-e", "/sys", "-e", "/dev", "-e", "/selinux", "/etc/selinux/targeted/contexts/files/file_contexts", "/"])
425 def apply(self
, ksselinux
):
426 if os
.path
.exists(self
.path("/usr/sbin/lokkit")):
427 args
= ["/usr/sbin/lokkit", "-f", "--quiet", "--nostart"]
429 if ksselinux
.selinux
== ksconstants
.SELINUX_ENFORCING
:
430 args
.append("--selinux=enforcing")
431 if ksselinux
.selinux
== ksconstants
.SELINUX_PERMISSIVE
:
432 args
.append("--selinux=permissive")
433 if ksselinux
.selinux
== ksconstants
.SELINUX_DISABLED
:
434 args
.append("--selinux=disabled")
438 self
.relabel(ksselinux
)
440 def get_image_size(ks
, default
= None):
442 for p
in ks
.handler
.partition
.partitions
:
443 if p
.mountpoint
== "/" and p
.size
:
446 return int(__size
) * 1024L * 1024L
450 def get_image_fstype(ks
, default
= None):
451 for p
in ks
.handler
.partition
.partitions
:
452 if p
.mountpoint
== "/" and p
.fstype
:
458 if not hasattr(ks
.handler
.device
, "deviceList"):
459 devices
.append(ks
.handler
.device
)
461 devices
.extend(ks
.handler
.device
.deviceList
)
464 for device
in devices
:
465 if not device
.moduleName
:
467 modules
.extend(device
.moduleName
.split(":"))
471 def get_timeout(ks
, default
= None):
472 if not hasattr(ks
.handler
.bootloader
, "timeout"):
474 if ks
.handler
.bootloader
.timeout
is None:
476 return int(ks
.handler
.bootloader
.timeout
)
478 def get_kernel_args(ks
, default
= "ro liveimg quiet"):
479 if not hasattr(ks
.handler
.bootloader
, "appendLine"):
481 if ks
.handler
.bootloader
.appendLine
is None:
483 return "%s %s" %(default
, ks
.handler
.bootloader
.appendLine
)
485 def get_default_kernel(ks
, default
= None):
486 if not hasattr(ks
.handler
.bootloader
, "default"):
488 if not ks
.handler
.bootloader
.default
:
490 return ks
.handler
.bootloader
.default
492 def get_repos(ks
, repo_urls
= {}):
494 for repo
in ks
.handler
.repo
.repoList
:
496 if hasattr(repo
, "includepkgs"):
497 inc
.extend(repo
.includepkgs
)
500 if hasattr(repo
, "excludepkgs"):
501 exc
.extend(repo
.excludepkgs
)
503 baseurl
= repo
.baseurl
504 mirrorlist
= repo
.mirrorlist
506 if repo
.name
in repo_urls
:
507 baseurl
= repo_urls
[repo
.name
]
510 if repos
.has_key(repo
.name
):
511 logging
.warn("Overriding already specified repo %s" %(repo
.name
,))
512 repos
[repo
.name
] = (repo
.name
, baseurl
, mirrorlist
, inc
, exc
)
514 return repos
.values()
516 def convert_method_to_repo(ks
):
518 ks
.handler
.repo
.methodToRepo()
519 except (AttributeError, kserrors
.KickstartError
):
522 def get_packages(ks
, required
= []):
523 return ks
.handler
.packages
.packageList
+ required
525 def get_groups(ks
, required
= []):
526 return ks
.handler
.packages
.groupList
+ required
528 def get_excluded(ks
, required
= []):
529 return ks
.handler
.packages
.excludedList
+ required
531 def get_partitions(ks
, required
= []):
532 return ks
.handler
.partition
.partitions
534 def ignore_missing(ks
):
535 return ks
.handler
.packages
.handleMissing
== ksconstants
.KS_MISSING_IGNORE
537 def exclude_docs(ks
):
538 return ks
.handler
.packages
.excludeDocs
541 if hasattr(ks
.handler
.packages
, "instLange"):
542 return ks
.handler
.packages
.instLange
543 elif hasattr(ks
.handler
.packages
, "instLangs"):
544 return ks
.handler
.packages
.instLangs
547 def get_post_scripts(ks
):
549 for s
in ks
.handler
.scripts
:
550 if s
.type != ksparser
.KS_SCRIPT_POST
:
555 def selinux_enabled(ks
):
556 return ks
.handler
.selinux
.selinux
== ksconstants
.SELINUX_ENFORCING